<?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>Forem: Jakkie Koekemoer</title>
    <description>The latest articles on Forem by Jakkie Koekemoer (@jakkie_koekemoer).</description>
    <link>https://forem.com/jakkie_koekemoer</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.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3547741%2F3ecbd835-9666-4974-84d6-a223eefceaaf.jpg</url>
      <title>Forem: Jakkie Koekemoer</title>
      <link>https://forem.com/jakkie_koekemoer</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/jakkie_koekemoer"/>
    <language>en</language>
    <item>
      <title>Zero-Downtime Domain Migration: A Developer's Automation Playbook for DNS, Email, and Rollback</title>
      <dc:creator>Jakkie Koekemoer</dc:creator>
      <pubDate>Fri, 17 Apr 2026 11:45:48 +0000</pubDate>
      <link>https://forem.com/jakkie_koekemoer/zero-downtime-domain-migration-a-developers-automation-playbook-for-dns-email-and-rollback-320d</link>
      <guid>https://forem.com/jakkie_koekemoer/zero-downtime-domain-migration-a-developers-automation-playbook-for-dns-email-and-rollback-320d</guid>
      <description>&lt;p&gt;Domain migrations fail at the seams between steps. The DNS records move fine, then someone flips the MX records before &lt;a href="https://www.name.com/support/articles/205934397-adding-an-spf-record" rel="noopener noreferrer"&gt;SPF&lt;/a&gt; propagates to the new zone. Or the registrar transfer fires while the old nameservers are still authoritative. The blast radius cloud include a &lt;a href="https://datatracker.ietf.org/doc/html/rfc7489" rel="noopener noreferrer"&gt;DMARC&lt;/a&gt; failure on transactional email, a broken &lt;a href="https://letsencrypt.org/docs/challenge-types/#dns-01-challenge" rel="noopener noreferrer"&gt;Let's Encrypt DNS-01 challenge&lt;/a&gt;, or a two-day authentication outage for OAuth redirects tied to a TXT verification record that got dropped mid-migration.&lt;/p&gt;

&lt;p&gt;The failure mode is consistent across these cases: DNS, email, and registrar changes executed as a single coupled batch, with no phase gates and no scripted reversion path. Engineers run through a checklist, something fails mid-flight, and the rollback gets improvised under pressure.&lt;/p&gt;

&lt;p&gt;This guide structures the migration as three sequential phases, each with a health check that must pass before the next begins. Every rollback artifact gets produced before the change it protects. The code examples run against the &lt;a href="https://docs.name.com/" rel="noopener noreferrer"&gt;name.com API&lt;/a&gt;, which covers all three phases through a single OpenAPI-spec'd surface: DNS CRUD via &lt;code&gt;/core/v1/domains/{domainName}/records&lt;/code&gt;, email forwarding via &lt;code&gt;/core/v1/domains/{domainName}/email/forwarding&lt;/code&gt;, and transfer operations including unlock, auth code retrieval, and &lt;code&gt;domain-transfer-status-change&lt;/code&gt; webhooks.&lt;/p&gt;

&lt;p&gt;Before touching anything live, map your blast radius per record type so health checks cover the right surface area:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Layer&lt;/th&gt;
&lt;th&gt;Records&lt;/th&gt;
&lt;th&gt;What breaks if dropped&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Traffic&lt;/td&gt;
&lt;td&gt;A, AAAA, CNAME&lt;/td&gt;
&lt;td&gt;HTTP/HTTPS endpoints, CDN origins&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;TLS&lt;/td&gt;
&lt;td&gt;CAA, DNS-01 TXT&lt;/td&gt;
&lt;td&gt;Certificate issuance and renewal&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Email delivery&lt;/td&gt;
&lt;td&gt;MX, TXT (SPF, DKIM, DMARC)&lt;/td&gt;
&lt;td&gt;Inbound routing, deliverability, DMARC enforcement&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Authentication&lt;/td&gt;
&lt;td&gt;TXT (Google, AWS, etc.)&lt;/td&gt;
&lt;td&gt;OAuth, service integrations, domain verification&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Run health check coverage against each row before declaring any phase complete.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmermaid.ink%2Fimg%2Fpako%3AeAEBkQNu_HsiY29kZSI6ImZsb3djaGFydCBURFxuICAgIEFbVGFrZSBab25lIFNuYXBzaG90XSAtLT4gQntWYWxpZGF0ZSBTbmFwc2hvdH1cbiAgICBCIC0tPnxBbGwgcmVjb3JkIHR5cGVzIHByZXNlbnR8IENbU3RhZ2UgVFRMIHRvIDMwMHNdXG4gICAgQiAtLT58TWlzc2luZyB0eXBlc3wgWDFbSGFsdDogRml4IHpvbmUgZmlyc3RdXG4gICAgQyAtLT4gRFtXYWl0IDI0aF1cbiAgICBEIC0tPiBFW1N0YWdlIFRUTCB0byA2MHNdXG4gICAgRSAtLT4gRltXYWl0IDFoXVxuICAgIEYgLS0-IEdbU2V0IEVtYWlsIEZvcndhcmRpbmcgUnVsZXNdXG4gICAgRyAtLT4gSFtVcGRhdGUgU1BGIC8gREtJTSAvIERNQVJDIFRYVF1cbiAgICBIIC0tPiBJe0RNQVJDIFByb3BhZ2F0aW9uIENoZWNrIHZpYSBkaWd9XG4gICAgSSAtLT58Q29uZmlybWVkfCBKW0ZsaXAgTVggUmVjb3Jkc11cbiAgICBJIC0tPnxUaW1lb3V0IGFmdGVyIDMwIGF0dGVtcHRzfCBYMltIYWx0IGFuZCBBbGVydF1cbiAgICBKIC0tPiBLe01YIFJlc29sdXRpb24gQ2hlY2t9XG4gICAgSyAtLT58UmVzb2x2ZXMgY29ycmVjdGx5fCBMW1VubG9jayBEb21haW5dXG4gICAgSyAtLT58RmFpbHN8IFJMMVtSb2xsYmFjayBNWF1cbiAgICBMIC0tPiBNW0dldCBBdXRoIENvZGVdXG4gICAgTSAtLT4gTltJbml0aWF0ZSBUcmFuc2ZlciBhdCBSZWNlaXZpbmcgUmVnaXN0cmFyXVxuICAgIE4gLS0-IE9bU3Vic2NyaWJlIFRyYW5zZmVyIFdlYmhvb2tdXG4gICAgTyAtLT4gUHtXZWJob29rIFN0YXR1cyBFdmVudH1cbiAgICBQIC0tPnxjb21wbGV0ZWR8IFFbUG9zdC1UcmFuc2ZlciBWYWxpZGF0aW9uXVxuICAgIFAgLS0-fGZhaWxlZCBvciBjYW5jZWxsZWR8IFJMMltBdXRvLVJlbG9jayBhbmQgUm9sbGJhY2tdIiwibWVybWFpZCI6IntcInRoZW1lXCI6XCJkZWZhdWx0XCJ9In1b8iNp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmermaid.ink%2Fimg%2Fpako%3AeAEBkQNu_HsiY29kZSI6ImZsb3djaGFydCBURFxuICAgIEFbVGFrZSBab25lIFNuYXBzaG90XSAtLT4gQntWYWxpZGF0ZSBTbmFwc2hvdH1cbiAgICBCIC0tPnxBbGwgcmVjb3JkIHR5cGVzIHByZXNlbnR8IENbU3RhZ2UgVFRMIHRvIDMwMHNdXG4gICAgQiAtLT58TWlzc2luZyB0eXBlc3wgWDFbSGFsdDogRml4IHpvbmUgZmlyc3RdXG4gICAgQyAtLT4gRFtXYWl0IDI0aF1cbiAgICBEIC0tPiBFW1N0YWdlIFRUTCB0byA2MHNdXG4gICAgRSAtLT4gRltXYWl0IDFoXVxuICAgIEYgLS0-IEdbU2V0IEVtYWlsIEZvcndhcmRpbmcgUnVsZXNdXG4gICAgRyAtLT4gSFtVcGRhdGUgU1BGIC8gREtJTSAvIERNQVJDIFRYVF1cbiAgICBIIC0tPiBJe0RNQVJDIFByb3BhZ2F0aW9uIENoZWNrIHZpYSBkaWd9XG4gICAgSSAtLT58Q29uZmlybWVkfCBKW0ZsaXAgTVggUmVjb3Jkc11cbiAgICBJIC0tPnxUaW1lb3V0IGFmdGVyIDMwIGF0dGVtcHRzfCBYMltIYWx0IGFuZCBBbGVydF1cbiAgICBKIC0tPiBLe01YIFJlc29sdXRpb24gQ2hlY2t9XG4gICAgSyAtLT58UmVzb2x2ZXMgY29ycmVjdGx5fCBMW1VubG9jayBEb21haW5dXG4gICAgSyAtLT58RmFpbHN8IFJMMVtSb2xsYmFjayBNWF1cbiAgICBMIC0tPiBNW0dldCBBdXRoIENvZGVdXG4gICAgTSAtLT4gTltJbml0aWF0ZSBUcmFuc2ZlciBhdCBSZWNlaXZpbmcgUmVnaXN0cmFyXVxuICAgIE4gLS0-IE9bU3Vic2NyaWJlIFRyYW5zZmVyIFdlYmhvb2tdXG4gICAgTyAtLT4gUHtXZWJob29rIFN0YXR1cyBFdmVudH1cbiAgICBQIC0tPnxjb21wbGV0ZWR8IFFbUG9zdC1UcmFuc2ZlciBWYWxpZGF0aW9uXVxuICAgIFAgLS0-fGZhaWxlZCBvciBjYW5jZWxsZWR8IFJMMltBdXRvLVJlbG9jayBhbmQgUm9sbGJhY2tdIiwibWVybWFpZCI6IntcInRoZW1lXCI6XCJkZWZhdWx0XCJ9In1b8iNp" alt="Diagram" width="821" height="2571"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Phase 1: Scripted DNS Snapshot, TTL Reduction, and Zone Replication
&lt;/h2&gt;

&lt;p&gt;The snapshot file is the precondition for every rollback decision in this guide. Take it before any other step.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Note: All examples use the &lt;a href="https://docs.name.com/guides/testing-environment" rel="noopener noreferrer"&gt;name.com sandbox environment&lt;/a&gt;. Once you're ready for production, change your API URL to &lt;code&gt;https://api.name.com&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;

&lt;span class="n"&gt;API_USER&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;your_username&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;API_TOKEN&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;your_api_token&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;DOMAIN&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;yourdomain.com&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;snapshot_zone&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;domain&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://api.dev.name.com/core/v1/domains/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;domain&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/records&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="n"&gt;resp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;auth&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;API_USER&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;API_TOKEN&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;raise_for_status&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;records&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;filename&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;snapshot_&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;strftime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;%Y%m%d_%H%M%S&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;.json&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;filename&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;w&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dump&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;records&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;indent&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Snapshot saved: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;filename&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;records&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;filename&lt;/span&gt;

&lt;span class="n"&gt;records&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;snapshot_file&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;snapshot_zone&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;DOMAIN&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Validate the snapshot before proceeding. A missing CAA record or absent DMARC TXT in the export means you're working from an incomplete source of truth.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;REQUIRED_TYPES&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;A&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;MX&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;TXT&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;CAA&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;CNAME&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;validate_snapshot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;records&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;found_types&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;records&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;records&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[])}&lt;/span&gt;
    &lt;span class="n"&gt;missing&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;REQUIRED_TYPES&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;found_types&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;missing&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Snapshot missing record types: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;missing&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Snapshot validated.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nf"&gt;validate_snapshot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;records&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With the snapshot confirmed, reduce TTL values well in advance so resolvers flush their caches before the actual cutover. Lower TTL to 300 seconds at least 24 hours before the migration window. Any resolver that previously cached a longer TTL needs time to expire its entry and pick up the shorter value before you cut over.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Note on minimum TTL limits: 300 seconds is the practical floor enforced by most registrar-managed DNS providers, including name.com. Attempting to set a lower value (such as 60s) may be silently ignored or rejected. Some dedicated DNS providers (e.g. Cloudflare non-Enterprise) support TTLs as low as 60 seconds, but this is not universal. Check your provider's documentation before assuming sub-300s values are accepted.&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;reduce_ttl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;domain&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;records&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;target_ttl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;record&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;records&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;records&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[]):&lt;/span&gt;
        &lt;span class="n"&gt;record_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://api.dev.name.com/core/v1/domains/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;domain&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/records/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;record_id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="n"&gt;payload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;host&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;host&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;answer&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;answer&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ttl&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;target_ttl&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="n"&gt;resp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;put&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;auth&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;raise_for_status&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;TTL reduced to &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;target_ttl&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;s across &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;records&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;records&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[]))&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; records&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Reduce to 300s (the minimum for most providers including name.com)
&lt;/span&gt;&lt;span class="nf"&gt;reduce_ttl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;DOMAIN&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;records&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;300&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;API_USER&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;API_TOKEN&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Waiting 24h for TTL propagation...&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;86400&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# You are now ready to cut over — changes will propagate within ~5 minutes
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Waiting the full 24 hours at 300s is necessary because resolvers that previously cached your records with a longer TTL (e.g. 86400s) won't re-check until that original TTL expires. Only after that window can you be confident all resolvers are working with the 300s value and will pick up your cutover quickly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Phase 2: Email Continuity, Forwarding Rules, and MX Gating
&lt;/h2&gt;

&lt;p&gt;Forwarding rules go up before MX records change. Any mail hitting the old domain during the transition window routes through to the destination address while the new MX records propagate. Set up forwarding using the name.com API's &lt;code&gt;create-email-forwarding&lt;/code&gt; endpoint:&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;-u&lt;/span&gt; &lt;span class="s2"&gt;"username:apitoken"&lt;/span&gt; &lt;span class="nt"&gt;--request&lt;/span&gt; POST &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--url&lt;/span&gt; https://api.dev.name.com/core/v1/domains/yourdomain.com/email/forwarding &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--header&lt;/span&gt; &lt;span class="s1"&gt;'Content-Type: application/json'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--data&lt;/span&gt; &lt;span class="s1"&gt;'
{
  "emailBox": "admin",
  "emailTo": "webmaster@example.com"
}
'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;/email/forwarding&lt;/code&gt; endpoint accepts one rule per request, so loop over your mailbox list when automating this across a team.&lt;/p&gt;

&lt;p&gt;Before touching MX records, get SPF, DKIM, and DMARC TXT records in place on the destination zone and confirmed as propagated. &lt;a href="https://support.google.com/mail/answer/14289100" rel="noopener noreferrer"&gt;Google and Yahoo mandated DMARC authentication for bulk senders in February 2024&lt;/a&gt;, and the enforcement is active. A migration that drops the DMARC TXT record before the MX cutover completes will route outbound mail to spam or trigger hard rejections, showing up as a deliverability incident in your postmaster dashboard hours after the fact.&lt;/p&gt;

&lt;p&gt;Update the auth records using the same &lt;code&gt;update-record&lt;/code&gt; endpoint from Phase 1, then run a propagation check before allowing the MX flip:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/bin/bash&lt;/span&gt;
&lt;span class="nv"&gt;DOMAIN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"yourdomain.com"&lt;/span&gt;
&lt;span class="nv"&gt;MAX_ATTEMPTS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;30
&lt;span class="nv"&gt;INTERVAL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;15

&lt;span class="k"&gt;for &lt;/span&gt;i &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;seq &lt;/span&gt;1 &lt;span class="nv"&gt;$MAX_ATTEMPTS&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do
    &lt;/span&gt;&lt;span class="nv"&gt;RESULT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;dig TXT _dmarc.&lt;span class="nv"&gt;$DOMAIN&lt;/span&gt; +short | &lt;span class="nb"&gt;tr&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'"'&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
    &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"[&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;date&lt;/span&gt; +%H:%M:%S&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;] Attempt &lt;/span&gt;&lt;span class="nv"&gt;$i&lt;/span&gt;&lt;span class="s2"&gt;: &lt;/span&gt;&lt;span class="nv"&gt;$RESULT&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$RESULT&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt;&lt;span class="s2"&gt;"v=DMARC1"&lt;/span&gt;&lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="o"&gt;]]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
        &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"DMARC record confirmed. Proceeding to MX flip."&lt;/span&gt;
        &lt;span class="nb"&gt;exit &lt;/span&gt;0
    &lt;span class="k"&gt;fi
    &lt;/span&gt;&lt;span class="nb"&gt;sleep&lt;/span&gt; &lt;span class="nv"&gt;$INTERVAL&lt;/span&gt;
&lt;span class="k"&gt;done

&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"DMARC propagation timed out after &lt;/span&gt;&lt;span class="k"&gt;$((&lt;/span&gt;MAX_ATTEMPTS &lt;span class="o"&gt;*&lt;/span&gt; INTERVAL&lt;span class="k"&gt;))&lt;/span&gt;&lt;span class="s2"&gt;s. Halting migration."&lt;/span&gt;
&lt;span class="nb"&gt;exit &lt;/span&gt;1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A code 1 exit means you halt everything. The same polling pattern applies to SPF and DKIM: run a &lt;code&gt;dig TXT&lt;/code&gt; check for each record before advancing. The MX flip goes through the &lt;code&gt;update-record&lt;/code&gt; endpoint. Patch the existing MX record IDs using &lt;code&gt;PUT /core/v1/domains/{domainName}/records/{id}&lt;/code&gt;, and leave the forwarding rules active for at least 48 hours post-cutover to catch any in-flight messages.&lt;/p&gt;

&lt;h2&gt;
  
  
  Phase 3: Programmatic Registrar Transfer, Webhooks, and Lock Sequencing
&lt;/h2&gt;

&lt;p&gt;The registrar transfer carries the longest irreversibility window, which makes sequencing critical. The correct order:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Unlock the domain&lt;/li&gt;
&lt;li&gt;Retrieve the auth code&lt;/li&gt;
&lt;li&gt;Initiate the transfer at the receiving registrar&lt;/li&gt;
&lt;li&gt;Subscribe to the &lt;code&gt;domain-transfer-status-change&lt;/code&gt; webhook
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;unlock_domain&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;domain&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://api.dev.name.com/core/v1/domains/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;domain&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="n"&gt;resp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;patch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;locked&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="n"&gt;auth&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;raise_for_status&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Domain &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;domain&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; unlocked.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_auth_code&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;domain&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://api.dev.name.com/core/v1/domains/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;domain&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/authCode&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="n"&gt;resp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;auth&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;raise_for_status&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;authCode&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nf"&gt;unlock_domain&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;DOMAIN&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;API_USER&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;API_TOKEN&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="n"&gt;auth_code&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get_auth_code&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;DOMAIN&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;API_USER&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;API_TOKEN&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Auth code retrieved: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;auth_code&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once you've initiated the transfer at the receiving registrar with the auth code, subscribe to the webhook so your system receives async status updates:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;subscribe_transfer_webhook&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;domain&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;callback_url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://api.dev.name.com/core/v1/notifications&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="n"&gt;payload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;event&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;domain-transfer-status-change&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;domainName&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;domain&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;url&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;callback_url&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;resp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;auth&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;raise_for_status&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Webhook subscribed: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nf"&gt;subscribe_transfer_webhook&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;DOMAIN&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://your-server.com/webhooks/transfer&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;API_USER&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;API_TOKEN&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The webhook delivers a payload with a &lt;code&gt;status&lt;/code&gt; field. Wire up a Flask endpoint to handle it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;flask&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Flask&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;jsonify&lt;/span&gt;

&lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Flask&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;__name__&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nd"&gt;@app.route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/webhooks/transfer&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;methods&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;POST&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;transfer_webhook&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;payload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;status&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;domain&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;domainName&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;failed&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cancelled&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Transfer &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; for &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;domain&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;. Triggering rollback.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nf"&gt;relock_domain&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;domain&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;completed&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Transfer completed for &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;domain&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;. Running post-transfer validation.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nf"&gt;run_post_transfer_checks&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;domain&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;jsonify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;received&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;}),&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If transfer initiation fails or the webhook fires a failure event, call &lt;code&gt;POST /core/v1/domains/{domainName}/actions/lock&lt;/code&gt; immediately. An unlocked domain sitting in limbo is an active attack surface.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;relock_domain&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;domain&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;auth&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;API_USER&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;API_TOKEN&lt;/span&gt;&lt;span class="p"&gt;)):&lt;/span&gt;
    &lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://api.dev.name.com/core/v1/domains/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;domain&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/actions/lock&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="n"&gt;resp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;auth&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;raise_for_status&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Domain &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;domain&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; re-locked.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;GoDaddy's API surfaces transfer status through polling only, which means a rollback trigger fires minutes after a failure event on an exposed domain. Namecheap's transfer API requires polling as well, with no webhook event support. The name.com &lt;a href="https://docs.name.com/api/v1/reference/webhook-notifications/domain-transfer-out-status-change#domain-transfer-out-status-change" rel="noopener noreferrer"&gt;&lt;code&gt;domain-transfer-status-change&lt;/code&gt;&lt;/a&gt; webhook fires immediately on status change, giving the auto-relock function enough headroom to run in under a second of the failure event. That speed matters when the domain is unlocked.&lt;/p&gt;

&lt;h2&gt;
  
  
  Have a Clear Rollback Plan: Snapshot Design and Reversion Procedures
&lt;/h2&gt;

&lt;p&gt;The rollback snapshot JSON from Phase 1 needs to contain everything required for a clean manual reversion. The minimum viable structure:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"captured_at"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2024-11-15T09:30:00Z"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"domain"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"yourdomain.com"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"nameservers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"ns1.name.com"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ns2.name.com"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"records"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"12345"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"host"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"A"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"answer"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"203.0.113.10"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"ttl"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;300&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"12346"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"host"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"MX"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"answer"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"mail.yourdomain.com"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"ttl"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;300&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"forwarding_rules"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"emailBox"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"admin"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"emailTo"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"admin@oldprovider.com"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Rollback scope by layer:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;DNS-level: Re-create divergent records from the snapshot using &lt;code&gt;POST /core/v1/domains/{domainName}/records&lt;/code&gt;. Compare the live zone against the snapshot by diffing &lt;code&gt;jq&lt;/code&gt;-extracted record sets from both sources.&lt;/li&gt;
&lt;li&gt;Email-level: Delete forwarding rules added during the migration via &lt;code&gt;DELETE /core/v1/domains/{domainName}/email/forwarding/{emailBox}&lt;/code&gt;, then restore original MX records from the snapshot using the record IDs stored in the JSON.&lt;/li&gt;
&lt;li&gt;Transfer-level: Re-lock the domain via &lt;code&gt;PATCH /core/v1/domains/{domain}&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Rollback triggers are human-evaluated conditions that require a deliberate go/no-go decision before execution. Automate the detection, but keep a person in the approval loop. Conditions that warrant investigation and a reversion decision:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;HTTPS endpoints returning 5xx errors for more than 10 minutes post-cutover&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;dig MX yourdomain.com&lt;/code&gt; returning empty or unexpected results 30 minutes after the flip&lt;/li&gt;
&lt;li&gt;DMARC reports showing authentication failure rates spiking above baseline&lt;/li&gt;
&lt;li&gt;DNS propagation incomplete after 90% of the expected window (typically 24h at 300s TTL)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Set a hard decision deadline: 2 hours post-cutover for DNS changes, 24 hours for the MX flip. After TTLs have propagated across the global resolver population, the cost of rolling back climbs substantially. Make the call while reversion is still cheap.&lt;/p&gt;

&lt;p&gt;The rollback runbook, executed manually and verified at each step:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Pull the snapshot: &lt;code&gt;cat snapshot_20241115_093000.json | jq '.records'&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Diff against live: &lt;code&gt;curl -s -u "user:token" "https://api.dev.name.com/core/v1/domains/yourdomain.com/records" | jq '.records' &amp;gt; live.json &amp;amp;&amp;amp; diff snapshot_records.json live.json&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Re-create any missing or divergent records via &lt;code&gt;POST /core/v1/domains/{domainName}/records&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Restore MX records to original values using the record IDs from the snapshot&lt;/li&gt;
&lt;li&gt;Re-lock the domain if transfer was initiated&lt;/li&gt;
&lt;li&gt;Run the propagation polling script to confirm resolution returns to pre-migration values&lt;/li&gt;
&lt;li&gt;Verify forwarding rules via &lt;code&gt;GET /core/v1/domains/{domainName}/email/forwarding/{emailBox}&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Execute each step individually, verify its output, and only proceed when the previous step's verification passes. This approach draws directly on the &lt;a href="https://www.multidots.com/blog/zero-downtime-migration/" rel="noopener noreferrer"&gt;blue-green deployment and automated rollback principles&lt;/a&gt; used in zero-downtime database migrations: staged state transitions with explicit validation gates between them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Observability: Propagation Polling, Divergence Alerts, and CI/CD Integration
&lt;/h2&gt;

&lt;p&gt;Poll two resolvers in parallel and log every result with a timestamp. Divergence between 8.8.8.8 and 1.1.1.1 during propagation is expected. Both converging to the correct value is your success condition.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/bin/bash&lt;/span&gt;
&lt;span class="nv"&gt;DOMAIN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"yourdomain.com"&lt;/span&gt;
&lt;span class="nv"&gt;EXPECTED_IP&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"203.0.113.10"&lt;/span&gt;
&lt;span class="nv"&gt;MAX_ITERATIONS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;48
&lt;span class="nv"&gt;INTERVAL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;300  &lt;span class="c"&gt;# 5 minutes&lt;/span&gt;
&lt;span class="nv"&gt;LOGFILE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"propagation_&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;date&lt;/span&gt; +%Y%m%d_%H%M%S&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;.log"&lt;/span&gt;

&lt;span class="k"&gt;for &lt;/span&gt;i &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;seq &lt;/span&gt;1 &lt;span class="nv"&gt;$MAX_ITERATIONS&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do
    &lt;/span&gt;&lt;span class="nv"&gt;TS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;date&lt;/span&gt; +%H:%M:%S&lt;span class="si"&gt;)&lt;/span&gt;
    &lt;span class="nv"&gt;GOOGLE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;dig @8.8.8.8 A &lt;span class="nv"&gt;$DOMAIN&lt;/span&gt; +short&lt;span class="si"&gt;)&lt;/span&gt;
    &lt;span class="nv"&gt;CF&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;dig @1.1.1.1 A &lt;span class="nv"&gt;$DOMAIN&lt;/span&gt; +short&lt;span class="si"&gt;)&lt;/span&gt;
    &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"[&lt;/span&gt;&lt;span class="nv"&gt;$TS&lt;/span&gt;&lt;span class="s2"&gt;] Google: &lt;/span&gt;&lt;span class="nv"&gt;$GOOGLE&lt;/span&gt;&lt;span class="s2"&gt; | Cloudflare: &lt;/span&gt;&lt;span class="nv"&gt;$CF&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | &lt;span class="nb"&gt;tee&lt;/span&gt; &lt;span class="nt"&gt;-a&lt;/span&gt; &lt;span class="nv"&gt;$LOGFILE&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$GOOGLE&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$EXPECTED_IP&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$CF&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$EXPECTED_IP&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
        &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Propagation confirmed on both resolvers."&lt;/span&gt; | &lt;span class="nb"&gt;tee&lt;/span&gt; &lt;span class="nt"&gt;-a&lt;/span&gt; &lt;span class="nv"&gt;$LOGFILE&lt;/span&gt;
        &lt;span class="nb"&gt;exit &lt;/span&gt;0
    &lt;span class="k"&gt;fi
    &lt;/span&gt;&lt;span class="nb"&gt;sleep&lt;/span&gt; &lt;span class="nv"&gt;$INTERVAL&lt;/span&gt;
&lt;span class="k"&gt;done

&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Propagation check failed after &lt;/span&gt;&lt;span class="k"&gt;$((&lt;/span&gt;MAX_ITERATIONS &lt;span class="o"&gt;*&lt;/span&gt; INTERVAL &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="m"&gt;3600&lt;/span&gt;&lt;span class="k"&gt;))&lt;/span&gt;&lt;span class="s2"&gt;h."&lt;/span&gt; | &lt;span class="nb"&gt;tee&lt;/span&gt; &lt;span class="nt"&gt;-a&lt;/span&gt; &lt;span class="nv"&gt;$LOGFILE&lt;/span&gt;
&lt;span class="nb"&gt;exit &lt;/span&gt;1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The log file this produces is your audit trail: timestamped evidence of when each record resolved to the new value. &lt;a href="https://www.enisa.europa.eu/topics/cybersecurity-policy/nis-2-directive" rel="noopener noreferrer"&gt;The EU NIS2 Directive&lt;/a&gt;, which took effect in October 2024, places DNS providers and registrars under incident reporting and change management obligations. An automatically timestamped log of every DNS state transition covers the audit trail requirement without additional tooling.&lt;/p&gt;

&lt;p&gt;Structure the full migration as a GitHub Actions workflow where each phase is a separate job with &lt;code&gt;needs:&lt;/code&gt; dependencies. Failed jobs halt the pipeline and surface the rollback job.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Domain Migration&lt;/span&gt;

&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;workflow_dispatch&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;snapshot&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v4&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Take zone snapshot&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;python scripts/snapshot_zone.py&lt;/span&gt;
        &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;NAME_COM_USER&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.NAME_COM_USER }}&lt;/span&gt;
          &lt;span class="na"&gt;NAME_COM_TOKEN&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.NAME_COM_TOKEN }}&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Validate snapshot&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;python scripts/validate_snapshot.py&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/upload-artifact@v4&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;zone-snapshot&lt;/span&gt;
          &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;snapshot_*.json&lt;/span&gt;

  &lt;span class="na"&gt;ttl_ramp&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;needs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;snapshot&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v4&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/download-artifact@v4&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;zone-snapshot&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Reduce TTL to 300s&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;python scripts/reduce_ttl.py --ttl &lt;/span&gt;&lt;span class="m"&gt;300&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Wait 24h&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;sleep &lt;/span&gt;&lt;span class="m"&gt;86400&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Reduce TTL to 60s&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;python scripts/reduce_ttl.py --ttl &lt;/span&gt;&lt;span class="m"&gt;60&lt;/span&gt;

  &lt;span class="na"&gt;email_gate&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;needs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ttl_ramp&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v4&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Set forwarding rules&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;bash scripts/set_forwarding.sh&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Check DMARC propagation&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;bash scripts/check_dmarc.sh&lt;/span&gt;

  &lt;span class="na"&gt;mx_flip&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;needs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;email_gate&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v4&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Update MX records&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;python scripts/flip_mx.py&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Verify MX resolution&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;bash scripts/verify_mx.sh&lt;/span&gt;

  &lt;span class="na"&gt;transfer_init&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;needs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mx_flip&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v4&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Unlock domain and get auth code&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;python scripts/unlock_and_auth.py&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Subscribe transfer webhook&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;python scripts/subscribe_webhook.py&lt;/span&gt;

  &lt;span class="na"&gt;rollback&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;if&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;failure()&lt;/span&gt;
    &lt;span class="na"&gt;needs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;snapshot&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;ttl_ramp&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;email_gate&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;mx_flip&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;transfer_init&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v4&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/download-artifact@v4&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;zone-snapshot&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Execute rollback&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;python scripts/rollback.py&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;needs:&lt;/code&gt; chain enforces the phase gate requirement without custom orchestration logic. The &lt;code&gt;if: failure()&lt;/code&gt; condition on the rollback job means it runs only when something upstream fails, and the snapshot artifact from the first job is available to it via &lt;code&gt;download-artifact&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Route 53 offers solid DNS scripting via Boto3, but it covers only the DNS layer, requires full AWS ecosystem buy-in, and provides no native email forwarding API. Cloudflare's DNS API is excellent but operates as a DNS host and proxy, covering only Phase 1 of this pipeline. The name.com API covers the DNS zone, email forwarding, and registrar transfer operations through a single authenticated surface, which is why the code examples above are all against it. Vercel, Replit, and Netlify run their domain operations on the same infrastructure.&lt;/p&gt;

&lt;h2&gt;
  
  
  Before You Touch Anything: Run the Zone Snapshot Right Now
&lt;/h2&gt;

&lt;p&gt;If you're planning a migration and haven't taken a snapshot yet, do it now. You need a name.com API token first. &lt;a href="https://www.name.com/account/settings/api" rel="noopener noreferrer"&gt;Generate one in the API settings section of your name.com account&lt;/a&gt;, then pull your current zone state to a timestamped file:&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;-s&lt;/span&gt; &lt;span class="nt"&gt;-u&lt;/span&gt; &lt;span class="s2"&gt;"username:token"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="s2"&gt;"https://api.dev.name.com/core/v1/domains/yourdomain.com/records"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  | jq &lt;span class="s1"&gt;'.'&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; snapshot_&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;date&lt;/span&gt; +%Y%m%d_%H%M%S&lt;span class="si"&gt;)&lt;/span&gt;.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Confirm the output JSON contains your A, MX, and TXT records before proceeding. That file is the precondition for the TTL ramp, the rollback diff, and the post-transfer validation. A migration without a verified pre-migration snapshot has no clean reversion path. Commit it to your repo and treat it as an immutable artifact for the duration of the operation.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://docs.name.com/guides/getting-started" rel="noopener noreferrer"&gt;Get started with the name.com API&lt;/a&gt; to grab your API key and pull your current DNS records in under five minutes.&lt;/p&gt;

&lt;p&gt;Have you run a domain migration that went sideways despite a solid checklist? Drop your experience in the comments, including where the seam broke and what you did to recover.&lt;/p&gt;

</description>
      <category>dns</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Document Workflow Automation: An Architectural Guide to Building API-Driven Document Pipelines</title>
      <dc:creator>Jakkie Koekemoer</dc:creator>
      <pubDate>Fri, 03 Apr 2026 16:24:45 +0000</pubDate>
      <link>https://forem.com/jakkie_koekemoer/document-workflow-automation-an-architectural-guide-to-building-api-driven-document-pipelines-4kon</link>
      <guid>https://forem.com/jakkie_koekemoer/document-workflow-automation-an-architectural-guide-to-building-api-driven-document-pipelines-4kon</guid>
      <description>&lt;p&gt;A PDF generation script that breaks on special characters. A cron job that retries failed document conversions by rerunning the entire job. An eSign flow tracked in a shared spreadsheet where "sent" means someone sent an email. These are the actual engineering artifacts that accumulate when document workflows grow faster than the architecture beneath them.&lt;/p&gt;

&lt;p&gt;The scale problem compounds fast. A team processing 200 contracts a month can survive on scripts and email hand-offs. At 2,000 contracts, those same workflows are the bottleneck. At 20,000, engineers are maintaining hacks that should have been replaced two years ago: retry logic bolted onto cron jobs, signing flows with no audit trail, and PDF generation that silently drops content when a CRM field contains a Unicode character.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://www.grandviewresearch.com/industry-analysis/intelligent-document-processing-market-report" rel="noopener noreferrer"&gt;global intelligent document processing market was valued at \$2.3B in 2024 and is projected to reach \$12.35B by 2030 at a 33.1% CAGR&lt;/a&gt;, not because AI is newly fashionable, but because manual document handling is a measurable operational ceiling. The organizations crossing that ceiling are adopting an architectural model, not just swapping individual tools.&lt;/p&gt;

&lt;p&gt;Most teams are missing a framework for assembling generation, conversion, and signing operations into a pipeline that's resilient, auditable, and testable. This guide provides that framework, then grounds it in working Python examples against a real REST API suite.&lt;/p&gt;

&lt;h2&gt;
  
  
  Anatomy of a Document Automation Pipeline: The Five Stages
&lt;/h2&gt;

&lt;p&gt;Every document workflow automation pipeline, regardless of domain, decomposes into five discrete stages. Get clear on this model before you write a single API call.&lt;/p&gt;

&lt;p&gt;Stage 1 is intake. You receive or capture the source data that will drive the document. This might be a webhook payload from your CRM when a deal closes, a form submission, or a batch export from an ERP system. Without schema validation, deduplication, and an observable queue, documents arrive out of order, get processed twice, or disappear without trace.&lt;/p&gt;

&lt;p&gt;Stage 2 is generation. You render a document from a template and the structured data from stage 1. Common outputs include contracts, invoices, compliance reports, and onboarding kits. The failure modes here are template version drift (production runs a different template version than staging), no validation of input data against the template's expected schema, and no idempotent retry path if the generation call fails partway through.&lt;/p&gt;

&lt;p&gt;Stage 3 is processing. You transform, extract from, or optimize the generated document. This covers format conversion (DOCX to PDF), content extraction for downstream indexing, compression, and linearization for fast web delivery. When processing steps chain with no error isolation, a failed compression step blocks the entire document from reaching signing.&lt;/p&gt;

&lt;p&gt;Stage 4 is signing. You route the document for signature, track signer status, and capture consent with a full audit trail. Manual polling for signer status, no webhook-driven callbacks, and no programmatic access to the audit log are the common failure modes you'll encounter when a compliance review is triggered.&lt;/p&gt;

&lt;p&gt;Stage 5 is archival and distribution. You store the signed document with a retention policy and push it to downstream systems: your DMS, CRM, or data warehouse. The failure modes are no content-addressed versioning, no record of which document version was signed, and no delivery confirmation to downstream consumers.&lt;/p&gt;

&lt;p&gt;Idempotency is a first-class requirement at every stage. Each operation should be safely retryable, meaning the same inputs produce the same output and a retried call doesn't create a duplicate document, signing request, or archive record. You implement idempotency in your orchestration layer by generating a unique key per document job and checking it before re-processing. The API doesn't handle this for you automatically.&lt;/p&gt;

&lt;p&gt;A well-designed document automation pipeline flows through these stages in sequence:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F34w7bheh4n8up1q19wt5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F34w7bheh4n8up1q19wt5.png" alt="Document automation pipeline" width="800" height="53"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;One constraint worth knowing upfront: the three APIs in this stack don't share a document ID namespace, and each stage boundary requires a file handoff. DocGen returns the rendered document as base64 in the response body. You decode it and either save it to disk or upload it directly to PDF Services. PDF Services returns a &lt;code&gt;resultDocumentId&lt;/code&gt; that you download as a file, then re-upload to eSign, which runs on a different host with different authentication. This handoff pattern makes each stage independently testable and replayable.&lt;/p&gt;

&lt;h2&gt;
  
  
  Architectural Decision Framework: Four Axes Before You Write Code
&lt;/h2&gt;

&lt;p&gt;Four decisions determine whether your document pipeline scales cleanly or becomes the thing your team rewrites in 18 months.&lt;/p&gt;

&lt;h3&gt;
  
  
  Axis 1: REST API vs. SDK
&lt;/h3&gt;

&lt;p&gt;Use REST APIs for cloud-native, horizontally scalable pipelines where document operations are stateless HTTP calls. Use an SDK for on-premise deployments, air-gapped environments, or latency-sensitive processing where network round-trips are a constraint. &lt;a href="https://developer-api.foxit.com" rel="noopener noreferrer"&gt;Foxit&lt;/a&gt; offers both: REST APIs for cloud-native pipelines and PDF SDKs for on-premise or air-gapped deployments, so this is a real architectural choice. If your document pipeline runs inside a regulated environment where data can't leave the network perimeter, the SDK is the correct answer regardless of how convenient the REST API is.&lt;/p&gt;

&lt;h3&gt;
  
  
  Axis 2: Synchronous vs. Asynchronous Processing
&lt;/h3&gt;

&lt;p&gt;This is the most consequential call you'll make, and it varies by stage within a single pipeline.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Factor&lt;/th&gt;
&lt;th&gt;Synchronous&lt;/th&gt;
&lt;th&gt;Asynchronous&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Document size&lt;/td&gt;
&lt;td&gt;Under ~10 pages&lt;/td&gt;
&lt;td&gt;Large or variable-length&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SLA requirement&lt;/td&gt;
&lt;td&gt;Sub-second response&lt;/td&gt;
&lt;td&gt;Variable completion time acceptable&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Typical use case&lt;/td&gt;
&lt;td&gt;Real-time contract preview&lt;/td&gt;
&lt;td&gt;Batch invoice processing&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Error handling&lt;/td&gt;
&lt;td&gt;Inline exception handling&lt;/td&gt;
&lt;td&gt;Dead-letter queue, retry on callback&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Foxit API example&lt;/td&gt;
&lt;td&gt;DocGen (returns document in response body)&lt;/td&gt;
&lt;td&gt;PDF Services (returns taskId, poll for result); eSign (webhook callback on folder execution)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The Foxit suite illustrates this split directly. DocGen is synchronous: POST your template and data payload, get the rendered document back immediately in the response body, with no taskId and no polling. PDF Services is asynchronous: a conversion call returns a &lt;code&gt;taskId&lt;/code&gt;, and you poll a status endpoint until the result is ready. eSign is asynchronous via webhooks: creating a folder returns immediately, and the API delivers a callback to your registered endpoint when all signers complete. Design your pipeline around this reality rather than assuming a uniform execution model across all three APIs.&lt;/p&gt;

&lt;h3&gt;
  
  
  Axis 3: Linear Pipeline vs. Event-Driven Architecture
&lt;/h3&gt;

&lt;p&gt;A linear pipeline, where stage A blocks until complete before stage B starts, works for simple three-stage flows with predictable volume and acceptable end-to-end latency. An event-driven pipeline, where each stage emits a completion event consumed by the next stage, is the right choice when you need error isolation (a failed stage 3 doesn't block stage 2 outputs from being replayed), partial replay (reprocess from stage 2 without regenerating the document), or parallel processing branches (send the same document to multiple downstream consumers simultaneously).&lt;/p&gt;

&lt;p&gt;For pipelines that start as linear but need to scale, &lt;a href="https://n8n.io" rel="noopener noreferrer"&gt;n8n&lt;/a&gt; is a practical bridge. You can call Foxit's REST APIs from n8n workflows via HTTP Request nodes, which lets you wire pipeline stages without writing custom glue code while you validate the workflow logic before committing to a fully coded implementation.&lt;/p&gt;

&lt;h3&gt;
  
  
  Axis 4: Error Handling Strategy for Document Pipelines
&lt;/h3&gt;

&lt;p&gt;Three components belong in your initial design, not bolted on afterward.&lt;/p&gt;

&lt;p&gt;The first is idempotency keys. Generate a unique key per document job (a UUID tied to the source record ID and timestamp works well) and check it before re-processing. If a worker crashes mid-job and the job re-queues, the idempotency key prevents duplicate processing.&lt;/p&gt;

&lt;p&gt;The second is dead-letter handling. Define what happens to a document that has failed three consecutive processing attempts. It should route to a dead-letter queue with the failure reason and enough context to replay it manually or trigger an alert.&lt;/p&gt;

&lt;p&gt;The third is a circuit breaker. If PDF Services returns 5xx responses on five consecutive calls within 30 seconds, stop sending requests and return a fast failure to the calling system. This prevents a degraded upstream API from exhausting your worker pool and cascading failures downstream. The &lt;a href="https://martinfowler.com/bliki/CircuitBreaker.html" rel="noopener noreferrer"&gt;circuit breaker pattern&lt;/a&gt; maps cleanly onto any stateless HTTP integration.&lt;/p&gt;

&lt;h2&gt;
  
  
  Building the Pipeline: Foxit APIs in Practice
&lt;/h2&gt;

&lt;p&gt;We'll use &lt;a href="https://developer-api.foxit.com" rel="noopener noreferrer"&gt;Foxit's PDF Services, DocGen, and eSign APIs&lt;/a&gt; for the examples below. The patterns translate to any REST-based document API, but these are the endpoints we'll call.&lt;/p&gt;

&lt;h3&gt;
  
  
  Document Generation with the DocGen API
&lt;/h3&gt;

&lt;p&gt;DocGen takes a DOCX template (encoded as base64) and a JSON data payload, and returns the rendered document immediately in the response body. There's no templateId concept, so you send the template inline with every request. That means you own template versioning. Keep your templates in version control and pin the version used for each job to your event log.&lt;/p&gt;

&lt;p&gt;The request uses &lt;code&gt;client_id&lt;/code&gt; and &lt;code&gt;client_secret&lt;/code&gt; as HTTP headers against &lt;code&gt;na1.fusion.foxit.com&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;
&lt;span class="c1"&gt;# Illustrative example - not production code
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;base64&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;generate_contract&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;template_path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;template_path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;rb&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;template_b64&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;base64&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;b64encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="nf"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;utf-8&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;payload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;outputFormat&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;pdf&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;documentValues&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;base64FileString&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;template_b64&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://na1.fusion.foxit.com/document-generation/api/GenerateDocumentBase64&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;client_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;YOUR_CLIENT_ID&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;client_secret&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;YOUR_CLIENT_SECRET&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Content-Type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;application/json&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;payload&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;raise_for_status&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;base64&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;b64decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;base64FileString&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

&lt;span class="c1"&gt;# Data pulled from your CRM or ERP; validate against your template schema before calling
&lt;/span&gt;&lt;span class="n"&gt;contract_data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;client_name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Acme Corp&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;contract_value&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;48000&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;effective_date&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;2025-09-01&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;payment_terms&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Net 30&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;pdf_bytes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;generate_contract&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;templates/msa_v3.docx&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;contract_data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Validate your data payload against the template's expected field schema before the API call. DocGen doesn't catch type errors or missing fields with a clean error response. You get a malformed document instead. A &lt;a href="https://docs.pydantic.dev/latest/" rel="noopener noreferrer"&gt;Pydantic&lt;/a&gt; model or &lt;a href="https://json-schema.org/" rel="noopener noreferrer"&gt;JSON Schema&lt;/a&gt; validation step before the POST saves significant debugging time.&lt;/p&gt;

&lt;h3&gt;
  
  
  PDF Processing with the PDF Services API
&lt;/h3&gt;

&lt;p&gt;The most common PDF Services operation is conversion, and the DOCX-to-PDF call is also the simplest entry point for teams new to the API. PDF Services uses a two-step pattern: upload the source file first to get a &lt;code&gt;documentId&lt;/code&gt;, then call the operation endpoint with that ID. Because operations are asynchronous, the call returns a &lt;code&gt;taskId&lt;/code&gt; that you poll until the result is available.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;
&lt;span class="c1"&gt;# Illustrative example - not production code
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;

&lt;span class="n"&gt;PDF_SERVICES_HOST&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://na1.fusion.foxit.com&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;HEADERS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;client_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;YOUR_CLIENT_ID&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;client_secret&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;YOUR_CLIENT_SECRET&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;upload_document&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;file_bytes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;filename&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;PDF_SERVICES_HOST&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/pdf-services/api/documents/upload&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;HEADERS&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;files&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;file&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;filename&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;file_bytes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;application/octet-stream&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;raise_for_status&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;documentId&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;poll_task&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;task_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;status_resp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;PDF_SERVICES_HOST&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/pdf-services/api/tasks/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;task_id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;HEADERS&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;status_resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;raise_for_status&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;status_data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;status_resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;status_data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;status&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;COMPLETED&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;status_data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;resultDocumentId&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="n"&gt;status_data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;status&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;FAILED&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;RuntimeError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Task failed: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;status_data&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;download_document&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;document_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;PDF_SERVICES_HOST&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/pdf-services/api/documents/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;document_id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/download&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;HEADERS&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;raise_for_status&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;convert_docx_to_pdf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;docx_bytes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;doc_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;upload_document&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;docx_bytes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;document.docx&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;PDF_SERVICES_HOST&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/pdf-services/api/documents/create/pdf-from-word&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;HEADERS&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Content-Type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;application/json&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;documentId&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;doc_id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;raise_for_status&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;result_doc_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;poll_task&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;taskId&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;download_document&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result_doc_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;extract_text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pdf_bytes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;doc_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;upload_document&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pdf_bytes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;document.pdf&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;PDF_SERVICES_HOST&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/pdf-services/api/documents/modify/pdf-extract&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;HEADERS&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Content-Type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;application/json&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;documentId&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;doc_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;extractType&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;TEXT&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;raise_for_status&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;result_doc_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;poll_task&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;taskId&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;download_document&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result_doc_id&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;utf-8&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;pdf-extract&lt;/code&gt; endpoint pulls text from the PDF (pass &lt;code&gt;extractType&lt;/code&gt; as TEXT, IMAGE, or PAGE depending on what you need). Both conversion and extraction follow the same upload, execute, poll, download cycle. Feed the text output to a downstream search index so the document is queryable immediately after processing.&lt;/p&gt;

&lt;h3&gt;
  
  
  Signature Orchestration with the eSign API
&lt;/h3&gt;

&lt;p&gt;The eSign API uses &lt;a href="https://oauth.net/2/" rel="noopener noreferrer"&gt;OAuth2&lt;/a&gt; rather than header-based authentication. Your first call exchanges &lt;code&gt;client_id&lt;/code&gt; and &lt;code&gt;client_secret&lt;/code&gt; for a Bearer token on a separate host (&lt;code&gt;na1.foxitesign.foxit.com&lt;/code&gt;).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;
&lt;span class="c1"&gt;# Illustrative example - not production code
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;flask&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Flask&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;flask_request&lt;/span&gt;

&lt;span class="n"&gt;ESIGN_HOST&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://na1.foxitesign.foxit.com&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_esign_token&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;client_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;client_secret&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;ESIGN_HOST&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/api/oauth2/access_token&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;grant_type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;client_credentials&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;client_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;client_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;client_secret&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;client_secret&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;raise_for_status&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;access_token&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;create_signing_folder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pdf_bytes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;signers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;folder_payload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;folderName&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;MSA - Acme Corp&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;parties&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;firstName&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;first_name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;lastName&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;last_name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;emailId&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;email&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;permission&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;FILL_FIELDS_AND_SIGN&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sequence&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sequence&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;signers&lt;/span&gt;
        &lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;ESIGN_HOST&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/api/folders/createfolder&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Authorization&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Bearer &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="n"&gt;files&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;file&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;contract.pdf&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pdf_bytes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;application/pdf&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;data&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;folder_payload&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;application/json&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;raise_for_status&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;folderId&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="c1"&gt;# Webhook handler receives the folder-executed event
&lt;/span&gt;&lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Flask&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;__name__&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nd"&gt;@app.route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/webhooks/esign&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;methods&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;POST&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;esign_webhook&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;event&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;flask_request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;event_type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;folder_executed&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;folder_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;folder_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="n"&gt;signed_doc_url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;documents&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;download_url&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="nf"&gt;archive_signed_document&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;folder_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;signed_doc_url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Register your webhook endpoint in the eSign developer portal settings. When a folder is executed (all signers complete), the API POSTs the event payload to your endpoint. Extract the signed document URL from the callback and pass it to your archival stage. The eSign API also exposes a folder activity history endpoint that returns a complete audit trail: signer identity, timestamp, IP address, and authentication method for every interaction with the folder.&lt;/p&gt;

&lt;h3&gt;
  
  
  Chaining the Pipeline Stages with Idempotency
&lt;/h3&gt;

&lt;p&gt;The file handoff between stages is explicit by design. This minimal orchestration wrapper chains all three stages and demonstrates the idempotency pattern:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;
&lt;span class="c1"&gt;# Illustrative example - not production code
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;uuid&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;run_document_pipeline&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;job_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;template_path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;signers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;idempotency_key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;job_id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;:&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;uuid&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;uuid4&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;is_already_processed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;idempotency_key&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt;  &lt;span class="c1"&gt;# Safe to retry
&lt;/span&gt;
    &lt;span class="c1"&gt;# Stage 2: Generate (DocGen returns PDF bytes synchronously)
&lt;/span&gt;    &lt;span class="n"&gt;pdf_bytes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;generate_contract&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;template_path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;log_pipeline_event&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;job_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;generated&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;hash_document&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pdf_bytes&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

    &lt;span class="c1"&gt;# Stage 3: Process (extract text for indexing; convert if needed)
&lt;/span&gt;    &lt;span class="n"&gt;extracted&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;extract_text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pdf_bytes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;index_document&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;job_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;extracted&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;log_pipeline_event&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;job_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;processed&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;hash_document&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pdf_bytes&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

    &lt;span class="c1"&gt;# Stage 4: Sign (eSign returns folder ID; completion arrives via webhook)
&lt;/span&gt;    &lt;span class="n"&gt;token&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get_esign_token&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;YOUR_CLIENT_ID&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;YOUR_CLIENT_SECRET&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;folder_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;create_signing_folder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pdf_bytes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;signers&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;log_pipeline_event&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;job_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sent_for_signature&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;folder_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="nf"&gt;mark_processed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;idempotency_key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For async pipelines handling thousands of documents per hour, replace direct function calls with queue messages. Each stage worker pulls a job from &lt;a href="https://redis.io/docs/latest/" rel="noopener noreferrer"&gt;Redis&lt;/a&gt; or &lt;a href="https://aws.amazon.com/sqs/" rel="noopener noreferrer"&gt;Amazon SQS&lt;/a&gt;, executes the API call, ACKs on success, and publishes a completion event to the next stage's queue. If a worker crashes mid-job, the unACKed message re-queues and the idempotency key prevents re-processing a document that's already been completed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Auditability and Compliance by Design
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://gdpr.eu/" rel="noopener noreferrer"&gt;GDPR&lt;/a&gt;, &lt;a href="https://www.hhs.gov/hipaa/index.html" rel="noopener noreferrer"&gt;HIPAA&lt;/a&gt;, and &lt;a href="https://www.aicpa-cima.com/topic/audit-assurance/audit-and-assurance-greater-than-soc-2" rel="noopener noreferrer"&gt;SOC 2 Type II&lt;/a&gt; each impose specific requirements around document lifecycle traceability. Retrofitting an audit layer onto a pipeline that wasn't designed for it takes far more work than building it in from the start.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://martinfowler.com/eaaDev/EventSourcing.html" rel="noopener noreferrer"&gt;event sourcing pattern&lt;/a&gt; fits document pipelines directly. Maintain an append-only log of every document event: &lt;code&gt;created&lt;/code&gt;, &lt;code&gt;converted&lt;/code&gt;, &lt;code&gt;sent_for_signature&lt;/code&gt;, &lt;code&gt;signed&lt;/code&gt;, &lt;code&gt;archived&lt;/code&gt;. Use a stable &lt;code&gt;document_id&lt;/code&gt; as the primary key. This log makes replay straightforward: if signing fails, you can replay from the processing output without regenerating the document from scratch. Each event record should include the stage name, timestamp, operator identity, and a SHA-256 hash of the document bytes at that stage.&lt;/p&gt;

&lt;p&gt;The SHA-256 hash at each stage is your tamper detection mechanism. If the hash of the document presented for signing doesn't match the hash recorded at generation, you have an integrity problem that's immediately visible. This satisfies document integrity requirements in regulated industries without any additional tooling.&lt;/p&gt;

&lt;p&gt;The Foxit eSign API's built-in audit trail captures signer identity, timestamp, IP address, and authentication method for every folder interaction. Query the folder activity history endpoint to retrieve this data and persist it in your own audit store alongside your pipeline event log. Storing it in your own system, rather than relying solely on the eSign provider's records, gives you a complete, portable audit trail that survives a provider migration.&lt;/p&gt;

&lt;h2&gt;
  
  
  Scaling Document Workflow Automation Without Rebuilding It
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Batch Ingestion
&lt;/h3&gt;

&lt;p&gt;Place incoming document jobs on a queue (&lt;a href="https://redis.io/docs/latest/develop/data-types/lists/" rel="noopener noreferrer"&gt;Redis list&lt;/a&gt; or &lt;a href="https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/FIFO-queues.html" rel="noopener noreferrer"&gt;SQS FIFO queue&lt;/a&gt;) and run a pool of stateless worker processes. Each worker pulls a job, executes the API call with an idempotency key, and ACKs on success. Dead-letter routing handles permanently failed documents.&lt;/p&gt;

&lt;p&gt;This pattern processes thousands of documents per hour without hammering the API or requiring coordination between workers. Because each REST API call is stateless, workers scale horizontally without any shared state. You add capacity by adding workers, not by redesigning the pipeline.&lt;/p&gt;

&lt;h3&gt;
  
  
  Credit Quota and Backoff
&lt;/h3&gt;

&lt;p&gt;Foxit's pricing model is credit-based: API calls consume credits, and calls pause when credits are exhausted until renewal or upgrade. Implement &lt;a href="https://aws.amazon.com/blogs/architecture/exponential-backoff-and-jitter/" rel="noopener noreferrer"&gt;exponential backoff with jitter&lt;/a&gt; on 5xx responses as a general practice for any REST API integration.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;
&lt;span class="c1"&gt;# Illustrative example - not production code
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;random&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;api_call_with_retry&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;max_retries&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;attempt&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;max_retries&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status_code&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;
        &lt;span class="n"&gt;wait&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt; &lt;span class="n"&gt;attempt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;uniform&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;wait&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;raise_for_status&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Log quota exhaustion as a separate metric category. Consistent credit exhaustion signals that you need to upgrade your plan, and you shouldn't have to dig through application logs to detect it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Observability
&lt;/h3&gt;

&lt;p&gt;Instrument each pipeline stage with three metrics: processing latency (time from job enqueue to stage completion), error rate per stage, and document volume per time window. Use structured JSON logging so stage failures are queryable without parsing free-text log lines. &lt;a href="https://opentelemetry.io/" rel="noopener noreferrer"&gt;OpenTelemetry&lt;/a&gt; makes it straightforward to emit these metrics in a vendor-neutral format.&lt;/p&gt;

&lt;p&gt;A document that enters the pipeline and never exits is a data integrity problem. Track in-flight documents explicitly: when a job enters signing, record it. When the eSign webhook fires, close the record. Any job that's been in stage 4 for longer than your expected SLA without a webhook callback warrants an alert, not just a log entry.&lt;/p&gt;

&lt;h2&gt;
  
  
  Build Your First Document Pipeline Stage This Week
&lt;/h2&gt;

&lt;p&gt;Moving from a handful of scripts to a production document pipeline is a smaller jump than it looks. You don't need all five stages on day one.&lt;/p&gt;

&lt;p&gt;Sign up for a free account at &lt;a href="https://developer-api.foxit.com" rel="noopener noreferrer"&gt;developer-api.foxit.com&lt;/a&gt; (no credit card required) to get API credentials in minutes. Then make a single conversion call: POST a DOCX file to the PDF Services endpoint using the Python example above and verify you get a valid PDF back. That one round-trip confirms your authentication, network path, and the core integration pattern before you write any orchestration code.&lt;/p&gt;

&lt;p&gt;From there, choose one document type in your system that's still generated or processed by hand and map it against the five-stage model from the second section. Start at the highest-friction stage, not necessarily stage 1. If generation is the bottleneck, test DocGen templates against real data payloads in the Developer Playground before writing integration code. If signing is the pain point, wire up eSign folder creation and a webhook handler to close the loop.&lt;/p&gt;

&lt;p&gt;Every pattern in this guide, idempotency keys, event-sourced audit logs, async stage handoffs, and circuit breakers, works with any document API stack. A single REST API suite that covers generation, processing, and signing reduces the number of auth models you manage, shrinks the integration surface, and gives you one consistent debugging path when something breaks across stages. That is what it looks like to treat document workflow automation as a first-class architectural concern instead of a pile of scripts that should have been retired two years ago.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://developer-api.foxit.com" rel="noopener noreferrer"&gt;Start building your first pipeline stage today.&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Frequently Asked Questions
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What is document workflow automation?
&lt;/h3&gt;

&lt;p&gt;Document workflow automation replaces manual, script-driven document operations (generation, conversion, signing, and archival) with a structured API-driven pipeline. Each stage is independently testable, retryable via idempotency keys, and observable through structured event logs. At scale (thousands of documents per hour), automation eliminates the bottlenecks created by cron jobs, shared spreadsheets, and one-off scripts.&lt;/p&gt;

&lt;h3&gt;
  
  
  When should I use a synchronous vs. asynchronous document API?
&lt;/h3&gt;

&lt;p&gt;Use synchronous APIs when you need sub-second responses for small documents, for example, real-time contract previews under approximately 10 pages. Use asynchronous APIs (polling or webhook-driven) for large or variable-length documents, batch invoice processing, or any workflow where variable completion time is acceptable. Many document API suites, including Foxit's, mix both models across different endpoints, so design each pipeline stage around the actual execution model of the specific API call it makes.&lt;/p&gt;

&lt;h3&gt;
  
  
  How do I make a document pipeline idempotent?
&lt;/h3&gt;

&lt;p&gt;Generate a unique key per document job (a UUID tied to the source record ID and timestamp works well) and check whether that key has already been processed before executing any stage. Store processed keys in a fast key-value store (Redis is a common choice). On retry, the idempotency check returns early without duplicating the document, signing request, or archive record. This is an orchestration-layer responsibility because the document API itself doesn't provide it automatically.&lt;/p&gt;

&lt;h3&gt;
  
  
  What compliance requirements apply to document pipelines?
&lt;/h3&gt;

&lt;p&gt;GDPR, HIPAA, and SOC 2 Type II each require document lifecycle traceability. Implement an append-only event log keyed by a stable &lt;code&gt;document_id&lt;/code&gt;, capturing stage name, timestamp, operator identity, and a SHA-256 hash of the document at each stage. For eSign specifically, store the provider's audit trail (signer identity, IP address, authentication method, timestamp) in your own system so the record is portable across provider migrations.&lt;/p&gt;




&lt;p&gt;If you've built a document pipeline at scale, I'd genuinely like to hear where the architecture broke down. Was it the signing flow, the retry logic, something at the intake stage? Drop a comment below or share what you'd do differently.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>python</category>
      <category>api</category>
      <category>devops</category>
    </item>
    <item>
      <title>The Complete Guide to CNAME Records: Zone Apex Limitations, ANAME Records, and Common Pitfalls</title>
      <dc:creator>Jakkie Koekemoer</dc:creator>
      <pubDate>Mon, 30 Mar 2026 15:50:46 +0000</pubDate>
      <link>https://forem.com/jakkie_koekemoer/the-complete-guide-to-cname-records-zone-apex-limitations-aname-records-and-common-pitfalls-3bd2</link>
      <guid>https://forem.com/jakkie_koekemoer/the-complete-guide-to-cname-records-zone-apex-limitations-aname-records-and-common-pitfalls-3bd2</guid>
      <description>&lt;p&gt;You're setting up your new blog on Heroku. Their docs tell you to add a CNAME pointing to &lt;code&gt;your-app.herokuapp.com&lt;/code&gt;. You open your DNS panel, create &lt;code&gt;www.yourdomain.com&lt;/code&gt; as a CNAME, and it works perfectly.&lt;/p&gt;

&lt;p&gt;Then you try to do the same thing for the root domain, &lt;code&gt;yourdomain.com&lt;/code&gt;, and hit an error: "CNAME records cannot be created for the root domain."&lt;/p&gt;

&lt;p&gt;This isn't a Heroku problem. It's not a bug in your DNS provider either. It's a rule baked into the DNS specification itself: a CNAME record can't coexist with other record types at the zone apex. Because the root of your domain must carry records like SOA and NS, a standard CNAME simply isn't allowed there.&lt;/p&gt;

&lt;p&gt;Modern DNS providers work around this with features called ALIAS, &lt;a href="https://www.name.com/support/articles/115010493967-adding-an-aname-alias-record" rel="noopener noreferrer"&gt;ANAME&lt;/a&gt;, or CNAME flattening. Platforms like Heroku also &lt;a href="https://devcenter.heroku.com/articles/custom-domains#rules-on-adding-domains" rel="noopener noreferrer"&gt;document how to configure root domain pointing&lt;/a&gt; using these mechanisms.&lt;/p&gt;

&lt;p&gt;That said, the underlying rule still matters. To understand why this restriction exists, and when it causes real production problems, you need to understand how CNAMEs actually work.&lt;/p&gt;

&lt;h2&gt;
  
  
  How CNAME Records Work
&lt;/h2&gt;

&lt;p&gt;A &lt;a href="https://www.name.com/support/articles/115004895548-adding-a-cname-record" rel="noopener noreferrer"&gt;CNAME (Canonical Name) record&lt;/a&gt; creates an alias. It tells DNS: "Don't answer this query directly. Go look up this other hostname instead."&lt;/p&gt;

&lt;p&gt;Here's what happens when someone visits &lt;code&gt;blog.example.com&lt;/code&gt; and you have a CNAME pointing to &lt;code&gt;app-123.platform.com&lt;/code&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Browser asks DNS: "What's the IP for &lt;code&gt;blog.example.com&lt;/code&gt;?"&lt;/li&gt;
&lt;li&gt;DNS responds: "I don't have an IP. Look up &lt;code&gt;app-123.platform.com&lt;/code&gt; instead."&lt;/li&gt;
&lt;li&gt;Browser asks DNS: "What's the IP for &lt;code&gt;app-123.platform.com&lt;/code&gt;?"&lt;/li&gt;
&lt;li&gt;DNS responds: "That's &lt;code&gt;203.0.113.45&lt;/code&gt;."&lt;/li&gt;
&lt;li&gt;Browser connects to &lt;code&gt;203.0.113.45&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;You can see this in action with the &lt;code&gt;dig&lt;/code&gt; command on a Linux or MacOS terminal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dig www.microsoft.com

…
&lt;span class="p"&gt;;;&lt;/span&gt; ANSWER SECTION:
www.microsoft.com.          2440    IN  CNAME   www.microsoft.com-c-3.edgekey.net.
www.microsoft.com-c-3.edgekey.net.  187 IN  CNAME   www.microsoft.com-c-3.edgekey.net.globalredir.akadns.net.
www.microsoft.com-c-3.edgekey.net.globalredir.akadns.net. 187 IN CNAME e13678.dscb.akamaiedge.net.
e13678.dscb.akamaiedge.net. 13  IN  A   2.22.197.228
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The response includes both the full CNAME chain and the final A record. Notice that multiple lookups happened. Every CNAME adds latency because each one requires an additional DNS query. For users on slow connections or far from your nameservers, that adds up.&lt;/p&gt;

&lt;p&gt;Why use a CNAME at all, then? Because cloud platforms can (and do) change IPs without warning. When Heroku moves your app to a new server, &lt;code&gt;app-123.herokuapp.com&lt;/code&gt; automatically resolves to the new IP. Your CNAME keeps working. An A record would break until you manually updated it.&lt;/p&gt;

&lt;p&gt;The tradeoff is speed versus flexibility. CNAMEs give you automatic IP updates at the cost of an extra lookup on every visit. Understanding the difference between these &lt;a href="https://www.name.com/support/articles/205516858-understanding-dns-record-types" rel="noopener noreferrer"&gt;DNS record types&lt;/a&gt; helps you make the right call for your infrastructure.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Zone Apex Problem
&lt;/h2&gt;

&lt;p&gt;If a DNS name has a CNAME record, it must not have any other record types associated with it.&lt;/p&gt;

&lt;p&gt;That rule comes from the original DNS specifications. &lt;a href="https://datatracker.ietf.org/doc/html/rfc1034" rel="noopener noreferrer"&gt;RFC 1034&lt;/a&gt; describes how CNAME records work and how resolvers process them. Operational guidance is made explicit in &lt;a href="https://datatracker.ietf.org/doc/html/rfc1912#section-2.4" rel="noopener noreferrer"&gt;RFC 1912, Section 2.4&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A CNAME record is not allowed to coexist with any other data.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The reason is architectural. A CNAME doesn't contain data the way an A or MX record does. It says, "this name is an alias for another host." If other records existed alongside a CNAME, resolvers would face conflicting instructions: follow the alias, or use the local data? The protocol can't do both.&lt;/p&gt;

&lt;p&gt;Your root domain, &lt;code&gt;example.com&lt;/code&gt;, must have two records to function at all:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://www.name.com/support/articles/205516858-understanding-dns-record-types" rel="noopener noreferrer"&gt;SOA (Start of Authority)&lt;/a&gt;&lt;/strong&gt;: Defines who manages the zone and controls how often it refreshes&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://www.name.com/support/articles/115005091047-adding-an-ns-record" rel="noopener noreferrer"&gt;NS (Name Server)&lt;/a&gt;&lt;/strong&gt;: Points to the nameservers that answer queries for your domain&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;These aren't optional. Without them, your domain doesn't exist on the internet. Trying to add a CNAME to &lt;code&gt;example.com&lt;/code&gt; asks DNS to simultaneously "ignore this name and look elsewhere" and "use these nameservers." That's a contradiction the protocol won't resolve.&lt;/p&gt;

&lt;p&gt;There's another consequence worth calling out: email. Your root domain needs &lt;a href="https://www.name.com/support/articles/115004729247-adding-an-mx-record" rel="noopener noreferrer"&gt;MX records&lt;/a&gt; to receive mail at &lt;code&gt;user@example.com&lt;/code&gt;. Add a CNAME, and those MX records become invalid. Email stops working.&lt;/p&gt;

&lt;p&gt;The practical impact hits hardest when you're deploying to platforms like Vercel, Netlify, or GitHub Pages. They give you a hostname like &lt;code&gt;your-project.vercel.app&lt;/code&gt; and tell you to point your domain there. For &lt;code&gt;www.example.com&lt;/code&gt;, that works fine. For &lt;code&gt;example.com&lt;/code&gt;, you're forced to choose between:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;An A record that breaks when the platform changes IPs&lt;/li&gt;
&lt;li&gt;Redirecting all traffic from &lt;code&gt;example.com&lt;/code&gt; to &lt;code&gt;www.example.com&lt;/code&gt;, losing the clean root URL&lt;/li&gt;
&lt;li&gt;Giving up on the root domain entirely&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;None of those options are good.&lt;/p&gt;

&lt;h2&gt;
  
  
  How name.com's ANAME Record Solves This
&lt;/h2&gt;

&lt;p&gt;name.com solves this with the &lt;a href="https://www.name.com/support/articles/115010493967-adding-an-aname-alias-record" rel="noopener noreferrer"&gt;ANAME record&lt;/a&gt;. It's not part of the DNS specification. It's a feature that name.com's nameservers provide specifically to work around the zone apex limitation.&lt;/p&gt;

&lt;p&gt;Here's how it works:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;You create an ANAME record in the name.com dashboard pointing to your target hostname, like &lt;code&gt;app.platform.com&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;name.com's nameservers periodically resolve that hostname to get its current IP address&lt;/li&gt;
&lt;li&gt;When a query comes in for your domain, name.com returns that IP as a standard A record&lt;/li&gt;
&lt;li&gt;To the rest of the internet, you have a normal A record. But it updates automatically whenever the target hostname changes IPs.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;From the browser's perspective, this is a single-step lookup. You get the speed of an A record with the flexibility of a CNAME.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Configuring an ANAME record:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Log in to your &lt;a href="https://www.name.com/account/create" rel="noopener noreferrer"&gt;name.com account&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Navigate to your domain's DNS settings&lt;/li&gt;
&lt;li&gt;Add a new record with these settings:

&lt;ul&gt;
&lt;li&gt;Type: &lt;code&gt;ANAME&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Host: &lt;code&gt;@&lt;/code&gt; (represents your root domain)&lt;/li&gt;
&lt;li&gt;Answer: &lt;code&gt;your-app.platform.com&lt;/code&gt; (your target hostname)&lt;/li&gt;
&lt;li&gt;TTL: &lt;code&gt;300&lt;/code&gt; (5 minutes, allowing quick updates)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Save the record&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmp5vnf2lo0jy636vsmmz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmp5vnf2lo0jy636vsmmz.png" alt="name.com ANAME example" width="771" height="98"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The record goes live within minutes. name.com handles the resolution in the background, checking for IP changes at regular intervals and updating the served A record automatically. Understanding &lt;a href="https://www.name.com/support/articles/115012317827-understanding-domain-propagation" rel="noopener noreferrer"&gt;domain propagation&lt;/a&gt; will tell you when your changes are visible globally.&lt;/p&gt;

&lt;p&gt;The key benefit: you can now point your root domain at a hostname that might change IPs, without violating DNS protocol rules or breaking your email. Your SOA and NS records stay in place, your MX records keep working, and users reach your site at &lt;code&gt;example.com&lt;/code&gt; without the &lt;code&gt;www&lt;/code&gt;. You can also read more about &lt;a href="https://www.name.com/support/articles/206104227-bare-cname-records" rel="noopener noreferrer"&gt;bare CNAME records&lt;/a&gt; and how name.com handles them.&lt;/p&gt;

&lt;p&gt;This is the same concept that Cloudflare calls "CNAME Flattening" and AWS Route53 calls an "Alias" record. The implementation differs, but the goal is identical: solve the zone apex CNAME problem.&lt;/p&gt;

&lt;h2&gt;
  
  
  Common Pitfalls and Conflicts
&lt;/h2&gt;

&lt;p&gt;Even when you're using CNAMEs correctly on subdomains, a few edge cases will break your setup if you're not careful.&lt;/p&gt;

&lt;h3&gt;
  
  
  MX Records and Wildcard CNAMEs
&lt;/h3&gt;

&lt;p&gt;A common concern is whether a wildcard CNAME conflicts with your email configuration. Consider this setup:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight conf"&gt;&lt;code&gt;*.&lt;span class="n"&gt;example&lt;/span&gt;.&lt;span class="n"&gt;com&lt;/span&gt;.    &lt;span class="m"&gt;300&lt;/span&gt;    &lt;span class="n"&gt;IN&lt;/span&gt;    &lt;span class="n"&gt;CNAME&lt;/span&gt;    &lt;span class="n"&gt;your&lt;/span&gt;-&lt;span class="n"&gt;app&lt;/span&gt;.&lt;span class="n"&gt;platform&lt;/span&gt;.&lt;span class="n"&gt;com&lt;/span&gt;.
&lt;span class="n"&gt;mail&lt;/span&gt;.&lt;span class="n"&gt;example&lt;/span&gt;.&lt;span class="n"&gt;com&lt;/span&gt;. &lt;span class="m"&gt;300&lt;/span&gt;    &lt;span class="n"&gt;IN&lt;/span&gt;    &lt;span class="n"&gt;MX&lt;/span&gt;       &lt;span class="m"&gt;10&lt;/span&gt; &lt;span class="n"&gt;mail&lt;/span&gt;-&lt;span class="n"&gt;server&lt;/span&gt;.&lt;span class="n"&gt;com&lt;/span&gt;.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This configuration is actually valid.&lt;/p&gt;

&lt;p&gt;The CNAME exclusivity rule applies per name, not per wildcard scope. &lt;code&gt;*.example.com&lt;/code&gt; and &lt;code&gt;mail.example.com&lt;/code&gt; are different owner names in the zone. According to &lt;a href="https://www.rfc-editor.org/rfc/rfc4592" rel="noopener noreferrer"&gt;RFC 4592&lt;/a&gt;, a wildcard record is only used when no explicit record exists for the queried name. Because &lt;code&gt;mail.example.com&lt;/code&gt; has its own MX record, the wildcard CNAME doesn't apply to it.&lt;/p&gt;

&lt;p&gt;Problems occur when you attach a CNAME and another record type to the exact same name:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight conf"&gt;&lt;code&gt;&lt;span class="n"&gt;mail&lt;/span&gt;.&lt;span class="n"&gt;example&lt;/span&gt;.&lt;span class="n"&gt;com&lt;/span&gt;. &lt;span class="m"&gt;300&lt;/span&gt; &lt;span class="n"&gt;IN&lt;/span&gt; &lt;span class="n"&gt;CNAME&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt;.&lt;span class="n"&gt;platform&lt;/span&gt;.&lt;span class="n"&gt;com&lt;/span&gt;.
&lt;span class="n"&gt;mail&lt;/span&gt;.&lt;span class="n"&gt;example&lt;/span&gt;.&lt;span class="n"&gt;com&lt;/span&gt;. &lt;span class="m"&gt;300&lt;/span&gt; &lt;span class="n"&gt;IN&lt;/span&gt; &lt;span class="n"&gt;MX&lt;/span&gt;    &lt;span class="m"&gt;10&lt;/span&gt; &lt;span class="n"&gt;mail&lt;/span&gt;-&lt;span class="n"&gt;server&lt;/span&gt;.&lt;span class="n"&gt;com&lt;/span&gt;.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is invalid. A name with a CNAME must not have any other records at that same label.&lt;/p&gt;

&lt;p&gt;One more thing worth knowing: the host specified in an MX record must ultimately resolve to an A or AAAA record. If &lt;code&gt;mail.example.com&lt;/code&gt; is listed as an MX target, it needs its own address record.&lt;/p&gt;

&lt;h3&gt;
  
  
  TXT Record Conflicts
&lt;/h3&gt;

&lt;p&gt;You can't add a TXT record to a name that already has a CNAME. The exclusivity rule applies to all record types, not just A and MX.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight conf"&gt;&lt;code&gt;&lt;span class="c"&gt;# This is invalid:
&lt;/span&gt;&lt;span class="n"&gt;app&lt;/span&gt;.&lt;span class="n"&gt;example&lt;/span&gt;.&lt;span class="n"&gt;com&lt;/span&gt;.    &lt;span class="m"&gt;300&lt;/span&gt;    &lt;span class="n"&gt;IN&lt;/span&gt;    &lt;span class="n"&gt;CNAME&lt;/span&gt;    &lt;span class="n"&gt;platform&lt;/span&gt;.&lt;span class="n"&gt;com&lt;/span&gt;.
&lt;span class="n"&gt;app&lt;/span&gt;.&lt;span class="n"&gt;example&lt;/span&gt;.&lt;span class="n"&gt;com&lt;/span&gt;.    &lt;span class="m"&gt;300&lt;/span&gt;    &lt;span class="n"&gt;IN&lt;/span&gt;    &lt;span class="n"&gt;TXT&lt;/span&gt;      &lt;span class="s2"&gt;"verification-code-123"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This causes real problems when a service asks you to verify domain ownership by adding a TXT record to a subdomain that already points to a platform via CNAME. The DNS provider will reject the TXT record outright.&lt;/p&gt;

&lt;p&gt;Common workarounds include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Verifying the root domain (&lt;code&gt;example.com&lt;/code&gt;) instead&lt;/li&gt;
&lt;li&gt;Using a provider-specific verification name like &lt;code&gt;_verification.example.com&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Verifying a different subdomain that isn't already aliased with a CNAME&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The rule is simple: if a record is a CNAME, it can't hold any other record types at the same label.&lt;/p&gt;

&lt;h3&gt;
  
  
  CNAME Loops
&lt;/h3&gt;

&lt;p&gt;Circular references cause queries to fail entirely.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight conf"&gt;&lt;code&gt;&lt;span class="c"&gt;# This creates an infinite loop:
&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;.&lt;span class="n"&gt;example&lt;/span&gt;.&lt;span class="n"&gt;com&lt;/span&gt;.    &lt;span class="m"&gt;300&lt;/span&gt;    &lt;span class="n"&gt;IN&lt;/span&gt;    &lt;span class="n"&gt;CNAME&lt;/span&gt;    &lt;span class="n"&gt;b&lt;/span&gt;.&lt;span class="n"&gt;example&lt;/span&gt;.&lt;span class="n"&gt;com&lt;/span&gt;.
&lt;span class="n"&gt;b&lt;/span&gt;.&lt;span class="n"&gt;example&lt;/span&gt;.&lt;span class="n"&gt;com&lt;/span&gt;.    &lt;span class="m"&gt;300&lt;/span&gt;    &lt;span class="n"&gt;IN&lt;/span&gt;    &lt;span class="n"&gt;CNAME&lt;/span&gt;    &lt;span class="n"&gt;a&lt;/span&gt;.&lt;span class="n"&gt;example&lt;/span&gt;.&lt;span class="n"&gt;com&lt;/span&gt;.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When a resolver tries to look up &lt;code&gt;a.example.com&lt;/code&gt;, it follows the CNAME to &lt;code&gt;b.example.com&lt;/code&gt;, which points back to &lt;code&gt;a.example.com&lt;/code&gt;. Most resolvers detect this after a few iterations and return an error. The site becomes unreachable.&lt;/p&gt;

&lt;p&gt;This seems obvious in a two-record example, but it happens accidentally in complex setups where subdomains point to other subdomains across different zones. Always trace the full chain before deploying changes. Use the &lt;a href="https://www.name.com/support/articles/360026049933-troubleshooting-network-and-dns-connectivity-windows-and-mac" rel="noopener noreferrer"&gt;DNS connectivity troubleshooting tools&lt;/a&gt; to verify your configuration before pushing it live.&lt;/p&gt;

&lt;h3&gt;
  
  
  Long CNAME Chains
&lt;/h3&gt;

&lt;p&gt;While not technically invalid, long CNAME chains hurt performance.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight conf"&gt;&lt;code&gt;&lt;span class="c"&gt;# Each step adds latency:
&lt;/span&gt;&lt;span class="n"&gt;app&lt;/span&gt;.&lt;span class="n"&gt;example&lt;/span&gt;.&lt;span class="n"&gt;com&lt;/span&gt;.        &lt;span class="m"&gt;300&lt;/span&gt;    &lt;span class="n"&gt;IN&lt;/span&gt;    &lt;span class="n"&gt;CNAME&lt;/span&gt;    &lt;span class="n"&gt;lb&lt;/span&gt;.&lt;span class="n"&gt;platform&lt;/span&gt;.&lt;span class="n"&gt;com&lt;/span&gt;.
&lt;span class="n"&gt;lb&lt;/span&gt;.&lt;span class="n"&gt;platform&lt;/span&gt;.&lt;span class="n"&gt;com&lt;/span&gt;.        &lt;span class="m"&gt;300&lt;/span&gt;    &lt;span class="n"&gt;IN&lt;/span&gt;    &lt;span class="n"&gt;CNAME&lt;/span&gt;    &lt;span class="n"&gt;region&lt;/span&gt;-&lt;span class="n"&gt;us&lt;/span&gt;.&lt;span class="n"&gt;platform&lt;/span&gt;.&lt;span class="n"&gt;com&lt;/span&gt;.
&lt;span class="n"&gt;region&lt;/span&gt;-&lt;span class="n"&gt;us&lt;/span&gt;.&lt;span class="n"&gt;platform&lt;/span&gt;.&lt;span class="n"&gt;com&lt;/span&gt;. &lt;span class="m"&gt;300&lt;/span&gt;    &lt;span class="n"&gt;IN&lt;/span&gt;    &lt;span class="n"&gt;A&lt;/span&gt;        &lt;span class="m"&gt;203&lt;/span&gt;.&lt;span class="m"&gt;0&lt;/span&gt;.&lt;span class="m"&gt;113&lt;/span&gt;.&lt;span class="m"&gt;45&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's three DNS lookups instead of one. Each lookup can take 50-100ms depending on network conditions. For users on mobile networks or in regions far from your nameservers, this adds up fast. Keep CNAME chains as short as possible.&lt;/p&gt;

&lt;h2&gt;
  
  
  Choosing the Right Record Type
&lt;/h2&gt;

&lt;p&gt;Here's a quick reference for the most common scenarios:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Scenario&lt;/th&gt;
&lt;th&gt;Record Type&lt;/th&gt;
&lt;th&gt;Example&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Subdomain pointing to IP address&lt;/td&gt;
&lt;td&gt;&lt;a href="https://www.name.com/support/articles/115004893508-adding-an-a-record" rel="noopener noreferrer"&gt;A Record&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;blog.example.com&lt;/code&gt; → &lt;code&gt;203.0.113.45&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Subdomain pointing to hostname&lt;/td&gt;
&lt;td&gt;&lt;a href="https://www.name.com/support/articles/115004895548-adding-a-cname-record" rel="noopener noreferrer"&gt;CNAME&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;blog.example.com&lt;/code&gt; → &lt;code&gt;app.platform.com&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Root domain pointing to IP address&lt;/td&gt;
&lt;td&gt;&lt;a href="https://www.name.com/support/articles/115004893508-adding-an-a-record" rel="noopener noreferrer"&gt;A Record&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;example.com&lt;/code&gt; → &lt;code&gt;203.0.113.45&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Root domain pointing to hostname&lt;/td&gt;
&lt;td&gt;&lt;a href="https://www.name.com/support/articles/115010493967-adding-an-aname-alias-record" rel="noopener noreferrer"&gt;ANAME&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;example.com&lt;/code&gt; → &lt;code&gt;app.platform.com&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;IPv6 address&lt;/td&gt;
&lt;td&gt;&lt;a href="https://www.name.com/support/articles/115005155208-adding-an-aaaa-record-ipv6" rel="noopener noreferrer"&gt;AAAA Record&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;example.com&lt;/code&gt; → &lt;code&gt;2001:0db8::1&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Email server&lt;/td&gt;
&lt;td&gt;&lt;a href="https://www.name.com/support/articles/115004729247-adding-an-mx-record" rel="noopener noreferrer"&gt;MX Record&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;example.com&lt;/code&gt; → &lt;code&gt;mail.example.com&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Domain verification&lt;/td&gt;
&lt;td&gt;&lt;a href="https://www.name.com/support/articles/115004972547-adding-a-txt-record" rel="noopener noreferrer"&gt;TXT Record&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;example.com&lt;/code&gt; → &lt;code&gt;"verification-code"&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;A quick checklist before you push any DNS changes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;If the record is for your root domain and needs to point to a hostname, use an ANAME&lt;/li&gt;
&lt;li&gt;If the record is for a subdomain that also needs MX or TXT records, use an A record instead of a CNAME&lt;/li&gt;
&lt;li&gt;If you're setting up a wildcard catch-all, confirm it doesn't conflict with specific subdomains you need for email or domain verification&lt;/li&gt;
&lt;li&gt;If your platform (Heroku, Vercel, Netlify) provides both a hostname and IP addresses, prefer the hostname with an ANAME or CNAME so you get automatic updates&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The most common mistake is trying to use a CNAME at the root domain. The second most common is using a CNAME on a subdomain that also needs to receive email. Both come from the same RFC 1034 rule: CNAMEs must be the only record for that name.&lt;/p&gt;

&lt;p&gt;For developers automating DNS management, the &lt;a href="https://docs.name.com/docs/api-overview" rel="noopener noreferrer"&gt;name.com API&lt;/a&gt; gives you programmatic access to &lt;a href="https://docs.name.com/docs/api-reference/dns/create-record" rel="noopener noreferrer"&gt;create DNS records&lt;/a&gt;, &lt;a href="https://docs.name.com/docs/api-reference/dns/update-record" rel="noopener noreferrer"&gt;update records&lt;/a&gt;, and &lt;a href="https://docs.name.com/docs/api-reference/dns/list-records" rel="noopener noreferrer"&gt;list all records&lt;/a&gt; for your domains without touching the dashboard.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;CNAMEs follow strict protocol-level rules that made sense in 1987 and still make sense today, even if they create friction in modern workflows. The zone apex restriction exists because your root domain requires SOA and NS records to function, and a CNAME can't legally coexist with those records.&lt;/p&gt;

&lt;p&gt;name.com's &lt;a href="https://www.name.com/support/articles/115010493967-adding-an-aname-alias-record" rel="noopener noreferrer"&gt;ANAME records&lt;/a&gt; handle the resolution on name.com's nameservers and serve the result as a standard A record. You get automatic IP updates when your platform changes infrastructure, with no protocol violations and no conflicts with your MX or NS records. It's a clean solution to a problem that trips up a lot of engineers the first time they encounter it.&lt;/p&gt;

</description>
      <category>cname</category>
      <category>webdev</category>
      <category>dns</category>
      <category>devops</category>
    </item>
    <item>
      <title>How to Build a Domain Registration Integration with Python and the name.com API</title>
      <dc:creator>Jakkie Koekemoer</dc:creator>
      <pubDate>Sat, 21 Mar 2026 15:04:41 +0000</pubDate>
      <link>https://forem.com/jakkie_koekemoer/how-to-build-a-domain-registration-integration-with-python-and-the-namecom-api-1g2j</link>
      <guid>https://forem.com/jakkie_koekemoer/how-to-build-a-domain-registration-integration-with-python-and-the-namecom-api-1g2j</guid>
      <description>&lt;p&gt;If you're building an app that lets users pick a custom domain, you have two options. You can send them off to a registrar's website and hope they come back. Or you can handle the whole flow yourself. Platforms like &lt;a href="https://vercel.com/docs/projects/domains" rel="noopener noreferrer"&gt;Vercel&lt;/a&gt;, &lt;a href="https://docs.replit.com/cloud-services/deployments/custom-domains#custom-domains" rel="noopener noreferrer"&gt;Replit&lt;/a&gt;, and &lt;a href="https://docs.netlify.com/domains-https/custom-domains/" rel="noopener noreferrer"&gt;Netlify&lt;/a&gt; chose the second path. They integrated domain search, registration, and DNS management directly into their products, &lt;a href="https://siliconangle.com/2025/12/09/name-com-expands-api-integrations-across-netlify-replit-vercel-bolt/" rel="noopener noreferrer"&gt;using name.com as the backend registrar&lt;/a&gt; to power it all.&lt;/p&gt;

&lt;p&gt;This guide shows you how to build that same integration. We'll walk through the complete domain lifecycle: authentication, availability search, registration, and DNS configuration, using the &lt;a href="https://docs.name.com/" rel="noopener noreferrer"&gt;name.com API&lt;/a&gt;. Python with the &lt;a href="https://docs.python-requests.org/en/latest/" rel="noopener noreferrer"&gt;&lt;code&gt;requests&lt;/code&gt; library&lt;/a&gt; is the primary language here, because it's the clearest way to show what's actually happening over the wire. That said, the same concepts apply in most languages, and you'll find equivalent code blocks in PHP, Node.js, and Ruby throughout.&lt;/p&gt;

&lt;p&gt;A quick note on scope: each language has its own conventions for package management and environment variable handling, but those details are out of scope here. Only the necessary code snippets are included.&lt;/p&gt;

&lt;p&gt;By the end, you'll have a working Domain Manager script that covers the full lifecycle in under 200 lines. No heavy SDK to install. No XML to parse. Just HTTP calls and JSON responses.&lt;/p&gt;

&lt;p&gt;Here's the lifecycle we're building:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsc599ifobnhwcmualk31.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsc599ifobnhwcmualk31.png" alt="Domain Registration Integration" width="227" height="709"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Section 1: Prerequisites and Authentication
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Getting Your API Credentials
&lt;/h3&gt;

&lt;p&gt;Before you write a single line of code, you need two things: your name.com username and an API token. Log in to your account, navigate to the &lt;a href="https://www.name.com/account/settings/api" rel="noopener noreferrer"&gt;API settings page&lt;/a&gt; via &lt;strong&gt;API &amp;gt; API Token Management&lt;/strong&gt;, and generate a new token. The username is the same one you use to log in, not your email address.&lt;/p&gt;

&lt;p&gt;Name.com provides a separate test environment at &lt;code&gt;https://api.dev.name.com&lt;/code&gt;, which runs on a different set of credentials you create at &lt;a href="https://www.name.com/reseller" rel="noopener noreferrer"&gt;www.name.com/reseller&lt;/a&gt;. I'd strongly recommend starting there. Accidentally registering a live domain while debugging your error handling is an expensive way to learn a lesson.&lt;/p&gt;

&lt;p&gt;Store your credentials as environment variables. Never hardcode them in source files.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# .env file (add this to .gitignore)&lt;/span&gt;
&lt;span class="nv"&gt;NAMECOM_USERNAME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;your_username
&lt;span class="nv"&gt;NAMECOM_API_TOKEN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;your_api_token
&lt;span class="nv"&gt;NAMECOM_API_URL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;https://api.dev.name.com  &lt;span class="c"&gt;# switch to api.name.com/ for production&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  The "Hello World" Request
&lt;/h3&gt;

&lt;p&gt;The name.com API uses &lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Authentication#basic_authentication_scheme" rel="noopener noreferrer"&gt;HTTP Basic Auth&lt;/a&gt;. Your username goes in as the username, your API token as the password. No OAuth dance, no token exchange. Just a standard header that &lt;code&gt;requests&lt;/code&gt; handles in one line.&lt;/p&gt;

&lt;p&gt;Here's a minimal client class that verifies your credentials are working:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Python&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;dotenv&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;load_dotenv&lt;/span&gt;

&lt;span class="nf"&gt;load_dotenv&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;NameComClient&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;base_url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;NAMECOM_API_URL&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;auth&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;NAMECOM_USERNAME&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
            &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;NAMECOM_API_TOKEN&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Content-Type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;application/json&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;hello&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Verify credentials by calling the hello endpoint.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
        &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;base_url&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/core/v1/hello&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;auth&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;raise_for_status&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="c1"&gt;# use your new NameComClient class
&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;NameComClient&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;hello&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run this and you should see your username echoed back. A &lt;code&gt;401&lt;/code&gt; means your token is wrong. A &lt;code&gt;404&lt;/code&gt; means the base URL is off.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;PHP (&lt;a href="https://docs.guzzlephp.org/en/stable/" rel="noopener noreferrer"&gt;GuzzleHttp&lt;/a&gt;)&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;
&lt;span class="k"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'vendor/autoload.php'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;GuzzleHttp\Client&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nv"&gt;$dotenv&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Dotenv\Dotenv&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;createImmutable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;__DIR__&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="s1"&gt;'/'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nv"&gt;$dotenv&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;load&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="nv"&gt;$client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
    &lt;span class="s1"&gt;'base_uri'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$_ENV&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'NAMECOM_API_URL'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="s1"&gt;'auth'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;$_ENV&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'NAMECOM_USERNAME'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nv"&gt;$_ENV&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'NAMECOM_API_TOKEN'&lt;/span&gt;&lt;span class="p"&gt;]],&lt;/span&gt;
    &lt;span class="s1"&gt;'headers'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'Content-Type'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'application/json'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;]);&lt;/span&gt;

&lt;span class="nv"&gt;$response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$client&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'/core/v1/hello'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="nv"&gt;$response&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getBody&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Node.js (&lt;a href="https://www.npmjs.com/package/axios" rel="noopener noreferrer"&gt;axios&lt;/a&gt;)&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;axios&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;axios&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;dotenv&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;config&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;axios&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;baseURL&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;NAMECOM_API_URL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;username&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;NAMECOM_USERNAME&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;password&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;NAMECOM_API_TOKEN&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/core/v1/hello&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Ruby (&lt;a href="https://github.com/lostisland/faraday" rel="noopener noreferrer"&gt;Faraday&lt;/a&gt;)&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'faraday'&lt;/span&gt;
&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'dotenv/load'&lt;/span&gt;
&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'json'&lt;/span&gt;

&lt;span class="n"&gt;conn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Faraday&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;url: &lt;/span&gt;&lt;span class="no"&gt;ENV&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'NAMECOM_API_URL'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
  &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;request&lt;/span&gt; &lt;span class="ss"&gt;:authorization&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:basic&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;ENV&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'NAMECOM_USERNAME'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="no"&gt;ENV&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'NAMECOM_API_TOKEN'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'Content-Type'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'application/json'&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'/core/v1/hello'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="no"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;body&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice what's missing from every one of these examples. No SDK import. No proprietary client library. No 50-page setup guide. You're using the standard HTTP library for your language of choice, with basic authentication. It doesn't get more straightforward than that.&lt;/p&gt;

&lt;h2&gt;
  
  
  Section 2: Checking Domain Availability (The Search Pattern)
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The Endpoint and What It Returns
&lt;/h3&gt;

&lt;p&gt;Domain availability is a POST call to the &lt;a href="https://docs.name.com/api/v1/reference/domains/search" rel="noopener noreferrer"&gt;&lt;code&gt;/domains:search&lt;/code&gt;&lt;/a&gt; endpoint. You POST a keyword, and name.com returns a list of domain suggestions with their availability status and pricing. The price data is why this matters. You don't just need to know &lt;em&gt;if&lt;/em&gt; a domain is available, you need to know &lt;em&gt;what it costs&lt;/em&gt; before you show it to a user.&lt;/p&gt;

&lt;p&gt;The response distinguishes between standard and premium domains. A premium domain like &lt;code&gt;car.com&lt;/code&gt; might run $50,000 at registration, while &lt;code&gt;mycoolstartup.com&lt;/code&gt; will cost considerably less. Your code needs to handle both cases, or you'll accidentally surface $50,000 domains to users expecting standard pricing.&lt;/p&gt;

&lt;h3&gt;
  
  
  Python Walkthrough
&lt;/h3&gt;

&lt;p&gt;Add this method to your &lt;code&gt;NameComClient&lt;/code&gt; class:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;search_domains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;keyword&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tlds&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
        Search for available domains matching a keyword.
        Returns a list of available domain options with pricing.
        &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;tlds&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;tlds&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;com&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;net&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;org&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;io&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

        &lt;span class="n"&gt;payload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;keyword&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;keyword&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tldFilter&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;tlds&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;timeout&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;10000&lt;/span&gt;  &lt;span class="c1"&gt;# milliseconds to wait for results
&lt;/span&gt;        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;base_url&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/core/v1/:domains:search&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;auth&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;payload&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;raise_for_status&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

        &lt;span class="n"&gt;results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;

        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;results&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[]):&lt;/span&gt;
            &lt;span class="n"&gt;domain_info&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;domain&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;domainName&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;available&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;purchasable&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;price&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;purchasePrice&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;premium&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;premium&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;

            &lt;span class="c1"&gt;# Flag premium domains clearly — they can cost orders of magnitude more
&lt;/span&gt;            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;domain_info&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;premium&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
                &lt;span class="n"&gt;domain_info&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;premium_price&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;premiumRenewalPrice&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;domain_info&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;available&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
                &lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;domain_info&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;results&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To use it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# search available domains
&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;NameComClient&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;search_domains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;mycoolstartup&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;[PREMIUM]&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;premium&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;domain&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;: $&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;price&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/year &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This returns all domains that are available and match or are similar to your keyword. The &lt;code&gt;purchasable&lt;/code&gt; field is your source of truth for availability. The &lt;code&gt;purchasePrice&lt;/code&gt; is in dollars. Premium domains will have &lt;code&gt;"premium": true&lt;/code&gt;. Filter those out or handle them separately depending on your use case.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;PHP&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;searchDomains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Client&lt;/span&gt; &lt;span class="nv"&gt;$client&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nv"&gt;$keyword&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;array&lt;/span&gt; &lt;span class="nv"&gt;$tlds&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'com'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'net'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'io'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="k"&gt;array&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;$response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$client&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'/core/v1/domains:search'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="s1"&gt;'json'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'keyword'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$keyword&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'tldFilter'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$tlds&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'timeout'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;10000&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="p"&gt;]);&lt;/span&gt;
    &lt;span class="nv"&gt;$data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;json_decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$response&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getBody&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;array_filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'results'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="p"&gt;[],&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$r&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$r&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'purchasable'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nv"&gt;$r&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'premium'&lt;/span&gt;&lt;span class="p"&gt;]));&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Use your new function&lt;/span&gt;
&lt;span class="nb"&gt;print_r&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;searchDomains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$client&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'mycoolstartup'&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Node.js&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;searchDomains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;keyword&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;tlds&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;com&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;net&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;io&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/core/v1/domains:search&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;keyword&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;tldFilter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;tlds&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;10000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;results&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="p"&gt;[]).&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;purchasable&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;premium&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Use your new function&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;searchDomains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;mycoolstartup&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Pause until the Promise resolves&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Logs "Resolved data"&lt;/span&gt;
&lt;span class="p"&gt;})();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Ruby&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;search_domains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;keyword&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tlds&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'com'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'net'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'io'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
  &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'/core/v1/domains:search'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
    &lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;keyword: &lt;/span&gt;&lt;span class="n"&gt;keyword&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;tldFilter: &lt;/span&gt;&lt;span class="n"&gt;tlds&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;timeout: &lt;/span&gt;&lt;span class="mi"&gt;10_000&lt;/span&gt; &lt;span class="p"&gt;}.&lt;/span&gt;&lt;span class="nf"&gt;to_json&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;body&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'results'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="p"&gt;[]).&lt;/span&gt;&lt;span class="nf"&gt;select&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'purchasable'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'premium'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="c1"&gt;# Use your new function&lt;/span&gt;
&lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;search_domains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'mycoolstartup'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Section 3: Domain Registration (The Transaction)
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What Domain Registration Actually Requires
&lt;/h3&gt;

&lt;p&gt;This is where things get more complex. Registering a domain isn't just naming it. &lt;a href="https://www.icann.org/resources/pages/registrars/registrants/en" rel="noopener noreferrer"&gt;ICANN&lt;/a&gt; requires contact information for four roles: Registrant, Admin, Tech, and Billing. Each contact needs a name, organization, address, phone number, and email. In most cases, all four roles will be the same person. If not, you'll need to expand the code to accept a different contact object for each role.&lt;/p&gt;

&lt;h3&gt;
  
  
  Python Walkthrough
&lt;/h3&gt;

&lt;p&gt;Add this method to your &lt;code&gt;NameComClient&lt;/code&gt; class:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;   &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;register_domain&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;domain_name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;contact_details&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;years&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
        Register a domain 
        &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
        &lt;span class="n"&gt;payload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; 
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;domain&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;domainName&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;domain_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;years&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;years&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;contacts&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; 
                        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tech&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;firstName&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;contact_details&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;firstName&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
                            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;lastName&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;contact_details&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;lastName&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
                            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;companyName&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;contact_details&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;companyName&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
                            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;address1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;contact_details&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;address1&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
                            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;address2&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;contact_details&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;address2&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
                            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;city&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;contact_details&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;city&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
                            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;state&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;contact_details&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;state&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
                            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;zip&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;contact_details&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;zip&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
                            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;country&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;contact_details&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;country&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
                            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;email&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;contact_details&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;email&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
                            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;phone&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;contact_details&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;phone&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
                        &lt;span class="p"&gt;}&lt;/span&gt;
                    &lt;span class="p"&gt;},&lt;/span&gt;
                    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;privacyEnabled&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt; 
            &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;base_url&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/core/v1/domains&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;auth&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;payload&lt;/span&gt;
            &lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;raise_for_status&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

        &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;exceptions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HTTPError&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;error_data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;

            &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;An error has occured while trying to register: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;domain_name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;The error is: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;error_data&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To use it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;NameComClient&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;contact1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;firstName&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Jonny&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;lastName&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;IT Guy&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;companyName&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;My Company&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;address1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;1234 Any Street&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;address2&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;None&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;city&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Anytown&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;state&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;NY&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;zip&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;11222&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;country&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;US&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;email&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;support@example.com&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;phone&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;+15555555&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;register_domain&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;mycoolstartup.com&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;contact_details&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;contact1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Registered: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;domain&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;domainName&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; — expires &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;domain&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;expireDate&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;ValueError&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Cannot register: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A &lt;code&gt;409 Conflict&lt;/code&gt; response means the domain was taken between your availability check and your registration attempt. It happens.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;PHP&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;registerDomain&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Client&lt;/span&gt; &lt;span class="nv"&gt;$client&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nv"&gt;$domain&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;array&lt;/span&gt; &lt;span class="nv"&gt;$contact_details&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nv"&gt;$years&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;array&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$client&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'/core/v1/domains'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="s1"&gt;'json'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
                &lt;span class="s2"&gt;"domain"&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
                    &lt;span class="s2"&gt;"domainName"&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$domain&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="s2"&gt;"years"&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$years&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="s2"&gt;"contacts"&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
                        &lt;span class="s2"&gt;"tech"&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
                            &lt;span class="s2"&gt;"firstName"&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$contact_details&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'firstName'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
                            &lt;span class="s2"&gt;"lastName"&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$contact_details&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'lastName'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
                            &lt;span class="s2"&gt;"companyName"&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$contact_details&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'companyName'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
                            &lt;span class="s2"&gt;"address1"&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$contact_details&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'address1'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
                            &lt;span class="s2"&gt;"address2"&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$contact_details&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'address2'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
                            &lt;span class="s2"&gt;"city"&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$contact_details&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'city'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
                            &lt;span class="s2"&gt;"state"&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$contact_details&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'state'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
                            &lt;span class="s2"&gt;"zip"&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$contact_details&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'zip'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
                            &lt;span class="s2"&gt;"country"&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$contact_details&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'country'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
                            &lt;span class="s2"&gt;"email"&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$contact_details&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'email'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
                            &lt;span class="s2"&gt;"phone"&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$contact_details&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'phone'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
                        &lt;span class="p"&gt;],&lt;/span&gt;
                    &lt;span class="p"&gt;],&lt;/span&gt;
                    &lt;span class="s2"&gt;"privacyEnabled"&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="p"&gt;],&lt;/span&gt;
            &lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="p"&gt;]);&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;json_decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$response&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getBody&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;\GuzzleHttp\Exception\ClientException&lt;/span&gt; &lt;span class="nv"&gt;$e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;json_decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$e&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getResponse&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getBody&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;\RuntimeException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Registration failed: '&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$body&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'message'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="s1"&gt;'unknown'&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Use your new function:&lt;/span&gt;
&lt;span class="nv"&gt;$contact_details&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="s2"&gt;"firstName"&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"Jonny"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;"lastName"&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"IT Guy"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;"companyName"&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"My Company"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;"address1"&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"1234 Any Street"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;"address2"&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"None"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;"city"&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"Anytown"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;"state"&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"NY"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;"zip"&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"11222"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;"country"&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"US"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;"email"&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"support@example.com"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;"phone"&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"+15555555"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;];&lt;/span&gt;

&lt;span class="nb"&gt;print_r&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;registerDomain&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$client&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'mycoolstartup.com'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$contact_details&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Node.js&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;registerDomain&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;domainName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;contact_details&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;years&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/core/v1/domains&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;domain&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;domainName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;domainName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;years&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;years&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;contacts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="na"&gt;tech&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="na"&gt;firstName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;   &lt;span class="nx"&gt;contact_details&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;firstName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;lastName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;    &lt;span class="nx"&gt;contact_details&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;lastName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;companyName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;contact_details&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;companyName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;address1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;    &lt;span class="nx"&gt;contact_details&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;address1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;address2&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;    &lt;span class="nx"&gt;contact_details&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;address2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;city&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;        &lt;span class="nx"&gt;contact_details&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;city&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;state&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;       &lt;span class="nx"&gt;contact_details&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;zip&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;         &lt;span class="nx"&gt;contact_details&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;zip&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;country&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;     &lt;span class="nx"&gt;contact_details&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;country&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;       &lt;span class="nx"&gt;contact_details&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;phone&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;       &lt;span class="nx"&gt;contact_details&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;phone&lt;/span&gt;
          &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="na"&gt;privacyEnabled&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Unknown error&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Registration failed: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Use your new function&lt;/span&gt;
&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;contact1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;firstName&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Jonny&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;lastName&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;IT Guy&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;companyName&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;My Company&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;address1&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;1234 Any Street&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;address2&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;None&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;city&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Anytown&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;state&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;NY&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;zip&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;11222&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;country&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;US&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;email&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;support@example.com&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;phone&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;+15555555&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;registerDomain&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;mycoolstartup.com&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;contact1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Pause until the Promise resolves&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Logs "Resolved data"&lt;/span&gt;
&lt;span class="p"&gt;})();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Ruby&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;register_domain&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;domain_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;contact_details&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;years&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;payload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="ss"&gt;domain: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="ss"&gt;domainName: &lt;/span&gt;&lt;span class="n"&gt;domain_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="ss"&gt;years: &lt;/span&gt;&lt;span class="n"&gt;years&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="ss"&gt;contacts: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="ss"&gt;tech: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="ss"&gt;firstName:   &lt;/span&gt;&lt;span class="n"&gt;contact_details&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:firstName&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
            &lt;span class="ss"&gt;lastName:    &lt;/span&gt;&lt;span class="n"&gt;contact_details&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:lastName&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
            &lt;span class="ss"&gt;companyName: &lt;/span&gt;&lt;span class="n"&gt;contact_details&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:companyName&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
            &lt;span class="ss"&gt;address1:    &lt;/span&gt;&lt;span class="n"&gt;contact_details&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:address1&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
            &lt;span class="ss"&gt;address2:    &lt;/span&gt;&lt;span class="n"&gt;contact_details&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:address2&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
            &lt;span class="ss"&gt;city:        &lt;/span&gt;&lt;span class="n"&gt;contact_details&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:city&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
            &lt;span class="ss"&gt;state:       &lt;/span&gt;&lt;span class="n"&gt;contact_details&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:state&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
            &lt;span class="ss"&gt;zip:         &lt;/span&gt;&lt;span class="n"&gt;contact_details&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:zip&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
            &lt;span class="ss"&gt;country:     &lt;/span&gt;&lt;span class="n"&gt;contact_details&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:country&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
            &lt;span class="ss"&gt;email:       &lt;/span&gt;&lt;span class="n"&gt;contact_details&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:email&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
            &lt;span class="ss"&gt;phone:       &lt;/span&gt;&lt;span class="n"&gt;contact_details&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:phone&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
          &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="ss"&gt;privacyEnabled: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'/core/v1/domains'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_json&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="s2"&gt;"Registration failed: &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="no"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;body&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="s1"&gt;'message'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="k"&gt;unless&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;
  &lt;span class="no"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;body&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="c1"&gt;# Use your new function&lt;/span&gt;
&lt;span class="n"&gt;contact1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="ss"&gt;firstName: &lt;/span&gt;&lt;span class="s2"&gt;"Jonny"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="ss"&gt;lastName: &lt;/span&gt;&lt;span class="s2"&gt;"IT Guy"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="ss"&gt;companyName: &lt;/span&gt;&lt;span class="s2"&gt;"My Company"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="ss"&gt;address1: &lt;/span&gt;&lt;span class="s2"&gt;"1234 Any Street"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="ss"&gt;address2: &lt;/span&gt;&lt;span class="s2"&gt;"None"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="ss"&gt;city: &lt;/span&gt;&lt;span class="s2"&gt;"Anytown"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="ss"&gt;state: &lt;/span&gt;&lt;span class="s2"&gt;"NY"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="ss"&gt;zip: &lt;/span&gt;&lt;span class="s2"&gt;"11222"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="ss"&gt;country: &lt;/span&gt;&lt;span class="s2"&gt;"US"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="ss"&gt;email: &lt;/span&gt;&lt;span class="s2"&gt;"support@example.com"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="ss"&gt;phone: &lt;/span&gt;&lt;span class="s2"&gt;"+15555555"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;register_domain&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'mycoolstartup.com'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;contact1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Section 4: Managing DNS Records (The Configuration)
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The Scenario
&lt;/h3&gt;

&lt;p&gt;You've registered &lt;code&gt;mycoolstartup.com&lt;/code&gt;. Now you need to point it at your server at &lt;code&gt;192.168.113.42&lt;/code&gt;. That means creating an A record with a TTL of 300 seconds. Here's how to do it programmatically.&lt;/p&gt;

&lt;h3&gt;
  
  
  Listing Existing DNS Records
&lt;/h3&gt;

&lt;p&gt;Before you add records, check what's already there. Name.com sets up default nameserver records when you register a domain, and you don't want to create conflicts.&lt;/p&gt;

&lt;p&gt;Add this method to your &lt;code&gt;NameComClient&lt;/code&gt; class:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;list_dns_records&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;domain_name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;List all DNS records for a domain.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
        &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;base_url&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/core/v1/domains/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;domain_name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/records&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;auth&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;raise_for_status&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;records&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[])&lt;/span&gt;

&lt;span class="c1"&gt;# Use your new function
&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;NameComClient&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;records&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;list_dns_records&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;mycoolstartup.com&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;record&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;records&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;type&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;host&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; -&amp;gt; &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;answer&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Creating an A Record
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;point_to_ip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;domain_name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ip_address&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;host&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
    Create an A record pointing a domain (or subdomain) to an IP address.
    host=&lt;/span&gt;&lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="s"&gt; targets the apex domain (@).
    host=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;www&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt; targets www.mycoolstartup.com.
    &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;payload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;host&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;host&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;A&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;answer&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ip_address&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ttl&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;300&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;# 5-minute TTL, sensible default, fast to propagate
&lt;/span&gt;    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;base_url&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/core/v1/domains/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;domain_name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/records&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;auth&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;payload&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;raise_for_status&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="c1"&gt;# Usage: point www to the new server
&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;NameComClient&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;point_to_ip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;mycoolstartup.com&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;192.168.113.42&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;host&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;www&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;A record created. DNS will propagate within ~10 minutes.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;PHP&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;point_to_ip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Client&lt;/span&gt; &lt;span class="nv"&gt;$client&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nv"&gt;$domain&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nv"&gt;$ip&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nv"&gt;$host&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;array&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;$response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$client&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"/core/v1/domains/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;$domain&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/records"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="s1"&gt;'json'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'host'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$host&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'type'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'A'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'answer'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$ip&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'ttl'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;300&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="p"&gt;]);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;json_decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$response&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getBody&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Use your new function&lt;/span&gt;
&lt;span class="nb"&gt;print_r&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;point_to_ip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$client&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'mycoolstartup.com'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'192.168.113.42'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'www'&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Node.js&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;point_to_ip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;domainName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ip&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;host&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`/core/v1/domains/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;domainName&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/records`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;host&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;A&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;answer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ip&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;ttl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;300&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Use your new function&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;point_to_ip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;mycoolstartup.com&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;192.168.13.42&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;www&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Pause until the Promise resolves&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Logs "Resolved data"&lt;/span&gt;
&lt;span class="p"&gt;})();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Ruby&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;point_to_ip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;domain_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ip&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;host&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"/core/v1/domains/&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;domain_name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/records"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
    &lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;host: &lt;/span&gt;&lt;span class="n"&gt;host&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;type: &lt;/span&gt;&lt;span class="s1"&gt;'A'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;answer: &lt;/span&gt;&lt;span class="n"&gt;ip&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;ttl: &lt;/span&gt;&lt;span class="mi"&gt;300&lt;/span&gt; &lt;span class="p"&gt;}.&lt;/span&gt;&lt;span class="nf"&gt;to_json&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="no"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;body&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="c1"&gt;# Use your new function&lt;/span&gt;
&lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;point_to_ip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'mycoolstartup.com'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'192.168.13.42'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'www'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Beyond A records, you can create &lt;a href="https://www.name.com/support/articles/205516858-understanding-dns-record-types" rel="noopener noreferrer"&gt;CNAME, MX, TXT, and NS records&lt;/a&gt; using the same endpoint. Just swap the &lt;code&gt;type&lt;/code&gt; field and set &lt;code&gt;answer&lt;/code&gt; accordingly. A TXT record for domain verification with &lt;a href="https://search.google.com/search-console/about" rel="noopener noreferrer"&gt;Google Search Console&lt;/a&gt; or &lt;a href="https://documentation.mailgun.com/en/latest/user_manual.html#verifying-your-domain" rel="noopener noreferrer"&gt;Mailgun&lt;/a&gt; follows the exact same pattern with &lt;code&gt;"type": "TXT"&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Section 5: Common Domain API Integration Patterns
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Handling Rate Limits
&lt;/h3&gt;

&lt;p&gt;The name.com API returns &lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/429" rel="noopener noreferrer"&gt;&lt;code&gt;429 Too Many Requests&lt;/code&gt;&lt;/a&gt; if you send too many calls in a short window. This probably won't come up in a standard single-user UI flow, but it gets real quickly when you're running bulk operations like checking hundreds of domains at once.&lt;/p&gt;

&lt;p&gt;The simplest fix is a retry loop with exponential backoff:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;api_request_with_retry&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;endpoint&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Make an API request with automatic retry on rate limiting.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;max_retries&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;attempt&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;max_retries&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;method&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;base_url&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/core/v1/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;endpoint&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;auth&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status_code&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;429&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;wait_seconds&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt; &lt;span class="n"&gt;attempt&lt;/span&gt;  &lt;span class="c1"&gt;# 1s, 2s, 4s
&lt;/span&gt;            &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Rate limited. Retrying in &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;wait_seconds&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;s...&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;wait_seconds&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;continue&lt;/span&gt;
        &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;raise_for_status&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;RuntimeError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Max retries exceeded after rate limiting.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For bulk domain checks, say you're building a feature that checks 50 TLD variants of a brand name, keep your batch size under 20 per request and add a &lt;code&gt;time.sleep(0.5)&lt;/code&gt; between calls. That's enough breathing room for most production workloads.&lt;/p&gt;

&lt;p&gt;Name.com also &lt;a href="https://docs.name.com/api/v1/reference/webhook-notifications/subscribe-to-notification" rel="noopener noreferrer"&gt;supports webhooks&lt;/a&gt;. You can configure an endpoint to receive a notification when a domain registration completes on the registry side. That's the production-grade version of this pattern: queue the task, let name.com ping you when it's done, then update your UI.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;You've just walked through the full domain lifecycle in four endpoints and fewer than 200 lines of Python. The PHP, Node.js, and Ruby versions are structurally identical: set up Basic Auth, send JSON, parse the response.&lt;/p&gt;

&lt;p&gt;That simplicity is the point. Legacy registrar APIs like &lt;a href="https://www.namecheap.com/support/api/intro/" rel="noopener noreferrer"&gt;Namecheap's&lt;/a&gt; require XML/SOAP parsing, a format that modern JSON-native languages handle awkwardly. &lt;a href="https://docs.aws.amazon.com/Route53/latest/APIReference/Welcome.html" rel="noopener noreferrer"&gt;AWS Route 53&lt;/a&gt; works, but Boto3's IAM configuration is significant overhead for a task that's fundamentally just "register a domain." The name.com RESTful JSON API means any language with a decent HTTP library can become a domain client in an afternoon.&lt;/p&gt;

&lt;p&gt;Get your API token from your &lt;a href="https://www.name.com/account/settings/api" rel="noopener noreferrer"&gt;account settings&lt;/a&gt; and start building. &lt;/p&gt;




&lt;h2&gt;
  
  
  Frequently Asked Questions
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What is a domain registration API?
&lt;/h3&gt;

&lt;p&gt;A domain registration API is a programmatic interface that lets developers search for domain availability, register domains, and manage DNS records directly within their application, without redirecting users to a registrar's website. The &lt;a href="https://docs.name.com/" rel="noopener noreferrer"&gt;name.com RESTful API&lt;/a&gt; exposes these capabilities over standard HTTPS with JSON request and response bodies.&lt;/p&gt;

&lt;h3&gt;
  
  
  Which Python library should I use for the name.com domain API?
&lt;/h3&gt;

&lt;p&gt;The &lt;a href="https://docs.python-requests.org/en/latest/" rel="noopener noreferrer"&gt;&lt;code&gt;requests&lt;/code&gt; library&lt;/a&gt; is the clearest choice for making HTTP calls to the name.com API. It handles Basic Auth in a single line and parses JSON responses natively. No SDK or domain-specific library is required.&lt;/p&gt;

&lt;h3&gt;
  
  
  How do I check domain availability programmatically?
&lt;/h3&gt;

&lt;p&gt;Send a &lt;code&gt;POST&lt;/code&gt; request to &lt;a href="https://docs.name.com/api/v1/reference/domains/search#search" rel="noopener noreferrer"&gt;&lt;code&gt;/domains:search&lt;/code&gt;&lt;/a&gt; with your keyword and an optional list of TLD filters. The response includes each domain's &lt;code&gt;purchasable&lt;/code&gt; status and &lt;code&gt;purchasePrice&lt;/code&gt;. Filter on &lt;code&gt;purchasable: true&lt;/code&gt; and check the &lt;code&gt;premium&lt;/code&gt; flag before surfacing results to users. Premium domains can cost orders of magnitude more than standard registrations.&lt;/p&gt;

&lt;h3&gt;
  
  
  What HTTP status codes should I handle from the name.com domain API?
&lt;/h3&gt;

&lt;p&gt;The most important ones: &lt;code&gt;401 Unauthorized&lt;/code&gt; (bad credentials), &lt;code&gt;400 Bad Request&lt;/code&gt; (invalid request data, usually a generic issue with the POST body), and &lt;code&gt;429 Too Many Requests&lt;/code&gt; (rate limit hit, implement exponential backoff).&lt;/p&gt;

</description>
      <category>api</category>
      <category>python</category>
      <category>howto</category>
    </item>
    <item>
      <title>Document Generation API: How to Automate Personalized Document Creation at Scale</title>
      <dc:creator>Jakkie Koekemoer</dc:creator>
      <pubDate>Mon, 16 Mar 2026 10:14:26 +0000</pubDate>
      <link>https://forem.com/jakkie_koekemoer/document-generation-api-how-to-automate-personalized-document-creation-at-scale-16cc</link>
      <guid>https://forem.com/jakkie_koekemoer/document-generation-api-how-to-automate-personalized-document-creation-at-scale-16cc</guid>
      <description>&lt;p&gt;Every company has the same hidden bottleneck: someone, somewhere, is manually building documents. They pull a client's name from the &lt;a href="https://en.wikipedia.org/wiki/Customer_relationship_management" rel="noopener noreferrer"&gt;CRM&lt;/a&gt;, paste it into a &lt;a href="https://word.cloud.microsoft/" rel="noopener noreferrer"&gt;Word template&lt;/a&gt;, double-check the date, adjust the logo placement, and export to &lt;a href="https://en.wikipedia.org/wiki/PDF" rel="noopener noreferrer"&gt;PDF&lt;/a&gt;. On a good day, that's an intern handling a manageable workload. On a bad day, it's an engineer who wired the entire layout into &lt;a href="https://itextpdf.com/" rel="noopener noreferrer"&gt;iText&lt;/a&gt; or &lt;a href="https://pdfkit.org/" rel="noopener noreferrer"&gt;PDFKit&lt;/a&gt;, and now Marketing needs the font changed across every document type.&lt;/p&gt;

&lt;p&gt;Both approaches share the same problem: they don't scale. They're manual workarounds dressed up as processes, and they collapse the moment volume jumps from a few hundred records to 50,000 invoices that need to ship overnight. Legacy Mail Merge tools hit the same wall.&lt;/p&gt;

&lt;p&gt;There's a cleaner path. A &lt;strong&gt;document generation API&lt;/strong&gt; turns document creation into a data pipeline: define the layout once in a template, feed it structured JSON, and let the API return a finished PDF or DOCX in milliseconds. No one touches the document by hand. No one copy-pastes a single field.&lt;/p&gt;

&lt;p&gt;This article explains how that pipeline works, which industries rely on it most, and how &lt;a href="https://developer-api.foxit.com/" rel="noopener noreferrer"&gt;Foxit's DocGen API&lt;/a&gt; makes it practical to implement, even if your team has never automated a document workflow before.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Is a Document Generation API?
&lt;/h2&gt;

&lt;p&gt;At its core, a &lt;strong&gt;document generation API&lt;/strong&gt; is a cloud service that combines two inputs (a template and a data payload) to produce one output (a finished document). The template controls the visual layer: layout, fonts, branding, and placeholder tokens for dynamic content. The data comes as JSON, with keys that map directly to those placeholders. The API engine merges the two and delivers a production-ready PDF or DOCX, typically in milliseconds.&lt;/p&gt;

&lt;p&gt;The formula is simple:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;Template &lt;span class="o"&gt;(&lt;/span&gt;Structure&lt;span class="o"&gt;)&lt;/span&gt; + JSON Data &lt;span class="o"&gt;(&lt;/span&gt;Content&lt;span class="o"&gt;)&lt;/span&gt; + API Engine &lt;span class="o"&gt;=&lt;/span&gt; Final Document
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Why use an API instead of building a local renderer? Two reasons.&lt;/p&gt;

&lt;p&gt;First, &lt;strong&gt;scalability&lt;/strong&gt;. The same API call that produces one invoice can produce 100,000 invoices. You don't manage rendering servers, worry about memory pressure from complex layouts, or debug pagination edge cases. The provider handles all of that.&lt;/p&gt;

&lt;p&gt;Second, &lt;strong&gt;separation of concerns&lt;/strong&gt;. Your legal team edits a liability clause directly in the Word template; no developer involvement required. Marketing swaps the logo without triggering a code deployment. The document's appearance lives entirely outside your codebase.&lt;/p&gt;

&lt;p&gt;Not every tool follows this model. Libraries like &lt;a href="https://pdfkit.org/" rel="noopener noreferrer"&gt;PDFKit&lt;/a&gt; and &lt;a href="https://pdfbox.apache.org/" rel="noopener noreferrer"&gt;Apache PDFBox&lt;/a&gt; take a code-first approach: you draw lines, position text boxes, and calculate column widths programmatically. That's manageable for static, single-page documents. It falls apart when tables grow to unpredictable lengths, when conditional sections depend on customer data, or when non-technical stakeholders need to change the design. The template-based API approach solves this by keeping design decisions in Word and logic in code, each where it belongs.&lt;/p&gt;

&lt;h2&gt;
  
  
  How Document Generation Automation Works
&lt;/h2&gt;

&lt;p&gt;The system breaks down into three layers. Understanding each one makes every integration decision clearer.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0g22c07mv9z6hwpl4tsw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0g22c07mv9z6hwpl4tsw.png" alt="Document Generation Automation" width="800" height="226"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Template Creation
&lt;/h3&gt;

&lt;p&gt;Modern document generation APIs like &lt;a href="https://developer-api.foxit.com/" rel="noopener noreferrer"&gt;Foxit's&lt;/a&gt; don't require you to write layout code. Instead, you design the document in Microsoft Word exactly as it should appear, then drop in double-bracket tokens wherever dynamic data should go.&lt;/p&gt;

&lt;p&gt;For an invoice template, the tokens might look like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;{{ companyName }}&lt;/code&gt;: the client's company name&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;{{ invoiceDate \@ MM/dd/yyyy }}&lt;/code&gt;: a formatted date like &lt;code&gt;01/15/2024&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;{{ totalDue \# Currency }}&lt;/code&gt;: a currency value like &lt;code&gt;$2,500.00&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The template is a standard &lt;code&gt;.docx&lt;/code&gt; file. Anyone on the team can open it, change the header font, move the logo, or reword a paragraph. None of those changes touch your application code.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Data Binding
&lt;/h3&gt;

&lt;p&gt;Your application fetches data from its source, whether that's a &lt;a href="https://www.salesforce.com/" rel="noopener noreferrer"&gt;Salesforce&lt;/a&gt; CRM, an &lt;a href="https://www.sap.com/" rel="noopener noreferrer"&gt;SAP&lt;/a&gt; ERP system, or a &lt;a href="https://www.postgresql.org/" rel="noopener noreferrer"&gt;PostgreSQL&lt;/a&gt; database, and formats it as JSON. The JSON keys correspond directly to the token names in the template. There's no transformation layer and no intermediate format to maintain.&lt;/p&gt;

&lt;p&gt;Here's a sample payload for the invoice template above:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"companyName"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Meridian Financial Group"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"invoiceDate"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2024-01-15"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"invoiceNumber"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"INV-00471"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"lineItems"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"API Integration Consulting"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"qty"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"unitPrice"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;150.0&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Compliance Review"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"qty"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"unitPrice"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;200.0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"totalDue"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;2500.0&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;companyName&lt;/code&gt;, &lt;code&gt;invoiceDate&lt;/code&gt;, and &lt;code&gt;totalDue&lt;/code&gt; in the JSON match the tokens in the template. The binding is 1:1.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Dynamic Template Logic: Loops and Formatting
&lt;/h3&gt;

&lt;p&gt;This is where a document generation API pulls ahead of basic find-and-replace tools.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Repeating tables&lt;/strong&gt; use loop delimiters. In &lt;a href="https://docs.developer-api.foxit.com/" rel="noopener noreferrer"&gt;Foxit's&lt;/a&gt; syntax, you place &lt;code&gt;{{TableStart:lineItems}}&lt;/code&gt; and &lt;code&gt;{{TableEnd:lineItems}}&lt;/code&gt; around the row that should repeat. The API walks through the &lt;code&gt;lineItems&lt;/code&gt; array in your JSON and generates one row for each entry, whether the array contains 2 items or 200. Within the loop, &lt;code&gt;{{ ROW_NUMBER }}&lt;/code&gt; provides automatic line numbering and &lt;code&gt;{{ SUM(ABOVE) }}&lt;/code&gt; calculates column totals.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Formatting specifiers&lt;/strong&gt; live inside the token syntax itself. The &lt;code&gt;\# Currency&lt;/code&gt; specifier converts &lt;code&gt;2500.00&lt;/code&gt; into &lt;code&gt;$2,500.00&lt;/code&gt;. The &lt;code&gt;\@ MM/dd/yyyy&lt;/code&gt; specifier handles date formatting without requiring any preprocessing in your application.&lt;/p&gt;

&lt;p&gt;The net effect: your templates handle variable-length tables, locale-aware formatting, and conditional logic entirely within Word. Your codebase stays focused on business logic, not document rendering.&lt;/p&gt;

&lt;h2&gt;
  
  
  Document Generation Use Cases by Industry
&lt;/h2&gt;

&lt;p&gt;Several industries depend on document generation in their critical workflows. These are the scenarios where automation delivers the fastest return.&lt;/p&gt;

&lt;h3&gt;
  
  
  Financial Services: Client Reports and Investment Summaries
&lt;/h3&gt;

&lt;p&gt;Consider a wealth management firm that produces quarterly performance reports for thousands of clients. The report template (header, chart placeholders, disclaimer, signature block) stays constant. The data varies per client: portfolio value, asset allocation, benchmark comparisons, year-to-date returns. A nightly batch job pulls each client's data from the portfolio management system, assembles JSON payloads, and sends POST requests to the generation API. By morning, 8,000 personalized PDFs sit in an &lt;a href="https://aws.amazon.com/s3/" rel="noopener noreferrer"&gt;Amazon S3&lt;/a&gt; bucket, ready for delivery.&lt;/p&gt;

&lt;h3&gt;
  
  
  Insurance: The Policy Packet
&lt;/h3&gt;

&lt;p&gt;When an insurance carrier issues a homeowner's policy, the output is a multi-section document: cover letter, declarations page, endorsements, and liability disclaimer. Each section can be a separate template. The API merges them into a single PDF at bind time, replacing the manual process where underwriters assembled packets by hand.&lt;/p&gt;

&lt;h3&gt;
  
  
  HR and Operations: Employee Onboarding
&lt;/h3&gt;

&lt;p&gt;The moment a new hire accepts an offer, the HRIS fires a webhook. The document generation service picks up the employee's details (name, role, start date, salary, benefits elections) and produces the complete onboarding packet: offer letter, benefits summary, I-9 instructions, and handbook acknowledgment. The new employee receives a personalized PDF bundle within seconds. No one in HR assembled it manually.&lt;/p&gt;

&lt;h3&gt;
  
  
  Sales: Branded Quotes and Contracts
&lt;/h3&gt;

&lt;p&gt;Sales teams know the "Export to PDF" routine: populate a spreadsheet, copy the data into Word, fix the formatting, and hope the branding holds. A document generation API replaces that entire cycle with a CRM-triggered workflow. When a rep marks a deal as "Proposal Sent," Salesforce fires a POST request with the deal data. The API returns a branded PDF with accurate pricing, the client's logo from the CRM record, and the correct contract terms for that deal tier.&lt;/p&gt;

&lt;h2&gt;
  
  
  Foxit DocGen: The Developer Experience
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://developer-api.foxit.com/" rel="noopener noreferrer"&gt;Foxit&lt;/a&gt; has spent over 20 years building PDF engines. Their DocGen API brings that experience to a cloud-based, word-template-to-PDF service designed for straightforward integration. The workflow has three steps.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1: Design Your Word Template
&lt;/h3&gt;

&lt;p&gt;Open Microsoft Word and build your document with &lt;code&gt;{{ token }}&lt;/code&gt; placeholders using Foxit's double-bracket syntax. Add format specifiers and loop delimiters directly in the document. No proprietary editor required.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 2: Send a POST Request
&lt;/h3&gt;

&lt;p&gt;The API endpoint is &lt;code&gt;/document-generation/api/GenerateDocumentBase64&lt;/code&gt;. Authentication uses &lt;code&gt;client_id&lt;/code&gt; and &lt;code&gt;client_secret&lt;/code&gt; passed as HTTP headers. The request body contains the base64-encoded template and your JSON data. Here's the full flow in Python:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;base64&lt;/span&gt;

&lt;span class="c1"&gt;# Load and encode the Word template
&lt;/span&gt;&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;invoice_template.docx&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;rb&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;template_b64&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;base64&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;b64encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="nf"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;utf-8&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# JSON data payload from your CRM or database
&lt;/span&gt;&lt;span class="n"&gt;document_values&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;companyName&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Meridian Financial Group&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;invoiceDate&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;2024-01-15&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;invoiceNumber&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;INV-00471&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;lineItems&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;description&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;API Integration Consulting&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;qty&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;unitPrice&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;150.00&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;description&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Compliance Review&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;qty&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;unitPrice&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;200.00&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;totalDue&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;2500.00&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# POST to Foxit DocGen API
# Replace HOST with the base URL from your Foxit developer console
&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;{HOST}/document-generation/api/GenerateDocumentBase64&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;client_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;YOUR_CLIENT_ID&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;client_secret&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;YOUR_CLIENT_SECRET&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Content-Type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;application/json&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;base64FileString&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;template_b64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;documentValues&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;document_values&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;outputFormat&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;pdf&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Decode and save
&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;pdf_bytes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;base64&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;b64decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;base64FileString&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;invoice_00471.pdf&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;wb&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pdf_bytes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Invoice generated successfully.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 3: Decode and Store the Response
&lt;/h3&gt;

&lt;p&gt;The API returns a base64-encoded document in the response. Decode it, write it to disk or push it to an &lt;a href="https://aws.amazon.com/s3/" rel="noopener noreferrer"&gt;Amazon S3&lt;/a&gt; bucket, and the job is done.&lt;/p&gt;

&lt;p&gt;One capability worth highlighting: Foxit supports &lt;strong&gt;DOCX output&lt;/strong&gt; alongside PDF. Most document generation APIs lock you into PDF, which is immutable once generated. DOCX output opens up a draft review workflow where a generated document can go to a human reviewer for light edits before finalization. That's particularly valuable in legal and HR contexts where someone needs to approve a clause or add a handwritten note. Sample code and SDKs cover Python, JavaScript, Java, C#, and PHP, with additional languages on the &lt;a href="https://developer-api.foxit.com/" rel="noopener noreferrer"&gt;developer portal&lt;/a&gt;. A Postman workspace is also available for testing without writing any code.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why the API Approach Beats Building It Yourself
&lt;/h2&gt;

&lt;p&gt;Switching from a hand-coded PDF renderer to a template-based API delivers three concrete improvements.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Compliance and accuracy.&lt;/strong&gt; Hard-coded PDF layouts invite human error on every update. A case mismatch between &lt;code&gt;{{ CustomerName }}&lt;/code&gt; and &lt;code&gt;{{ customerName }}&lt;/code&gt; silently produces blank fields. With the API approach, data flows directly from your database into the document through structured bindings. There's no manual copy-paste step where someone transposes a loan amount or miskeys a policy limit.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Speed at scale.&lt;/strong&gt; Local rendering libraries like &lt;a href="https://pdfkit.org/" rel="noopener noreferrer"&gt;PDFKit&lt;/a&gt; process documents sequentially on your server. Rendering time increases with document complexity, especially for multi-page tables. At 10,000 documents, even small per-document delays add up to minutes of blocked processing. A cloud-based document generation API distributes rendering across managed infrastructure. Producing 50,000 invoices overnight becomes a scheduling decision, not a compute constraint.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Maintenance goes to the right people.&lt;/strong&gt; When Marketing wants to update the invoice footer, they open the Word template and make the change themselves. When Legal revises a liability clause, they do the same. No developer writes code, no one triggers a redeployment, and no one runs regression tests because a text block moved three pixels. That's the compounding benefit of the template-first model: every design iteration happens without a developer ticket.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;If your team is still manually assembling documents in Word, or maintaining a custom PDF renderer that breaks on edge cases, the template-plus-API pattern is a practical fix that doesn't require rearchitecting your system.&lt;/p&gt;

&lt;p&gt;One trend worth keeping an eye on: &lt;strong&gt;combining document generation with generative AI&lt;/strong&gt;. A few teams are already using LLMs to draft personalized narrative sections (like a portfolio summary or a client recommendation), then feeding that text as a JSON field into the document generation API. The LLM writes the prose; the API controls the formatting, branding, and layout. It's a way to get dynamic, personalized content without sacrificing consistency.&lt;/p&gt;

&lt;p&gt;If you've gone through this transition, or you're still running a hand-rolled PDF generator, I'd be curious to hear what tipped the balance. What made your team decide to change approaches?&lt;/p&gt;

</description>
      <category>api</category>
    </item>
    <item>
      <title>Dynamic Data Masking: Use Cases, Limitations, and What to Do Instead</title>
      <dc:creator>Jakkie Koekemoer</dc:creator>
      <pubDate>Fri, 13 Mar 2026 16:00:36 +0000</pubDate>
      <link>https://forem.com/jakkie_koekemoer/dynamic-data-masking-use-cases-limitations-and-what-to-do-instead-4laj</link>
      <guid>https://forem.com/jakkie_koekemoer/dynamic-data-masking-use-cases-limitations-and-what-to-do-instead-4laj</guid>
      <description>&lt;p&gt;Let’s imagine two everyday scenarios. A customer support agent pulls up an order record. They need the order history, but there's no reason for them to see the full credit card number. A developer is debugging a user profile issue, but shouldn't have access to the actual customer's email address.&lt;/p&gt;

&lt;p&gt;The common solution for this is Dynamic Data Masking (DDM). Instead of the real credit card number, the database shows &lt;code&gt;****-****-****-3456&lt;/code&gt;. Instead of the real email, it shows &lt;code&gt;j***@email.com&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;You can think of DDM as a filter sitting between your database and the user querying it. The underlying data stays untouched, DDM just controls what different users get to see. For customer support teams working with live data, it works well since you don't want them to see the sensitive details. But for engineering and development teams, it tends to create more problems than it solves because masked data can break query logic, skew test results, and make it harder to reproduce real bugs accurately. If you're deciding how to protect sensitive data in your development workflow, understanding that distinction matters a lot.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is Dynamic Data Masking?
&lt;/h2&gt;

&lt;p&gt;Dynamic Data Masking modifies data at &lt;em&gt;query time&lt;/em&gt; based on your DB policies. The underlying data stays intact on disk. When someone queries a table, the database engine intercepts the SELECT and rewrites the output before returning results.&lt;/p&gt;

&lt;p&gt;The diagram below shows this flow:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fz49progbmb181v1oubnb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fz49progbmb181v1oubnb.png" alt="Dynamic Data Masking" width="800" height="475"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://learn.microsoft.com/en-us/sql/relational-databases/security/dynamic-data-masking?view=sql-server-ver16" rel="noopener noreferrer"&gt;Microsoft SQL Server&lt;/a&gt; implements DDM using column-level masking functions defined in the schema:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- Define masked columns directly in schema&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;customers&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="nb"&gt;INT&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;email&lt;/span&gt; &lt;span class="nb"&gt;VARCHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;MASKED&lt;/span&gt; &lt;span class="k"&gt;WITH&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;FUNCTION&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'email()'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;credit_card&lt;/span&gt; &lt;span class="nb"&gt;VARCHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;MASKED&lt;/span&gt; &lt;span class="k"&gt;WITH&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;FUNCTION&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'partial(0,"XXXX-XXXX-XXXX-",4)'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;-- Grant unmask permission for specific roles&lt;/span&gt;
&lt;span class="k"&gt;GRANT&lt;/span&gt; &lt;span class="n"&gt;UNMASK&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;customers&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;TO&lt;/span&gt; &lt;span class="n"&gt;support_role&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://postgresql-anonymizer.readthedocs.io/en/latest/dynamic_masking/" rel="noopener noreferrer"&gt;PostgreSQL Anonymizer&lt;/a&gt; uses security labels to define rules:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- You need the PostgreSQL Anonymizer extension installed and enabled&lt;/span&gt;
&lt;span class="c1"&gt;-- Mark columns with masking functions&lt;/span&gt;
&lt;span class="k"&gt;SECURITY&lt;/span&gt; &lt;span class="n"&gt;LABEL&lt;/span&gt; &lt;span class="k"&gt;FOR&lt;/span&gt; &lt;span class="n"&gt;anon&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="k"&gt;COLUMN&lt;/span&gt; &lt;span class="n"&gt;customer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt; 
  &lt;span class="k"&gt;IS&lt;/span&gt; &lt;span class="s1"&gt;'MASKED WITH FUNCTION anon.fake_email()'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;-- Enable transparent masking for specific roles&lt;/span&gt;
&lt;span class="k"&gt;SECURITY&lt;/span&gt; &lt;span class="n"&gt;LABEL&lt;/span&gt; &lt;span class="k"&gt;FOR&lt;/span&gt; &lt;span class="n"&gt;anon&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="k"&gt;ROLE&lt;/span&gt; &lt;span class="n"&gt;analyst&lt;/span&gt; &lt;span class="k"&gt;IS&lt;/span&gt; &lt;span class="s1"&gt;'MASKED'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There are three common patterns for DDM masking:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Full masking&lt;/strong&gt; replaces the entire value — so &lt;code&gt;john.doe@email.com&lt;/code&gt; becomes &lt;code&gt;NULL&lt;/code&gt; or &lt;code&gt;*****&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Partial masking&lt;/strong&gt; hides only the sensitive part — so &lt;code&gt;1234-5678-9012-3456&lt;/code&gt; becomes &lt;code&gt;xxxx-xxxx-xxxx-3456&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Randomization&lt;/strong&gt; swaps the real value with fake but realistic-looking data, so "John Smith" might become "Robert Johnson", from a dictionary of fake names.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It's important to understand that your database still runs all queries against the real, unmasked data. JOINs, aggregations, and WHERE filters all work on the actual values in your RDBMS tables. Masking only takes place at the very end, just before the results are sent back to the relevant user.&lt;/p&gt;

&lt;h2&gt;
  
  
  Dynamic vs. Static Data Masking
&lt;/h2&gt;

&lt;p&gt;With &lt;strong&gt;Dynamic Data Masking&lt;/strong&gt;, the original data stays untouched in your database. Masking happens on the fly when data is retrieved, and what users see depends on their permissions. On the other hand, &lt;strong&gt;Static Data Masking&lt;/strong&gt; works differently: it permanently transforms the data itself, creating a separate sanitized copy of the database where sensitive values are replaced with fake or masked data.&lt;/p&gt;

&lt;p&gt;That architectural difference determines which approach fits your situation. The diagram below shows both:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F02g15jii7gxe9ts6a7yg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F02g15jii7gxe9ts6a7yg.png" alt="Dynamic vs. Static Data Masking" width="800" height="140"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Dynamic Data Masking transforms data in flight
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Advantages:&lt;/strong&gt; You're always working with current production data. There's no extra storage needed, and you maintain a single source of truth.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Disadvantages:&lt;/strong&gt; It adds CPU overhead of 2-10% per query at runtime. Managing role-based access control can get complex. And it's fundamentally vulnerable to inference attacks: where someone deduces sensitive information by analyzing patterns in the data they &lt;em&gt;can&lt;/em&gt; see.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; Production operational access, customer support dashboards, and read-only reporting where users can't write their own arbitrary queries.&lt;/p&gt;

&lt;h3&gt;
  
  
  Static Data Masking transforms data at rest
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Advantages:&lt;/strong&gt; No runtime overhead once the initial transformation is done. It's immune to inference attacks since the sensitive data is replaced entirely. And it's safe to share freely with developers.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Disadvantages:&lt;/strong&gt; Historically, creating a full masked copy of your database could take hours. The data starts going stale the moment the copy is created. And setting up the ETL pipeline adds its own layer of complexity.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; &lt;a href="https://xata.io/realistic-staging-dev" rel="noopener noreferrer"&gt;Development environments&lt;/a&gt;, &lt;a href="https://xata.io/documentation/tutorials/create-staging-replica" rel="noopener noreferrer"&gt;staging databases&lt;/a&gt;, testing, and QA. Basically any scenario where developers need write access or the ability to run ad hoc queries.&lt;/p&gt;

&lt;p&gt;Static masking has long had a staleness problem though. If generating a masked copy of a 200GB database takes 4 hours, your developers are waiting half a day just to get test data. And by the time that copy is ready, production has already moved on. That delay is a big reason why many teams turned to DDM for development workflows, even knowing its security tradeoffs.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem with DDM for Developers
&lt;/h2&gt;

&lt;p&gt;Here's what &lt;a href="https://learn.microsoft.com/en-us/sql/relational-databases/security/dynamic-data-masking?view=sql-server-ver16" rel="noopener noreferrer"&gt;Microsoft's documentation&lt;/a&gt; warns about DDM explicitly:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Dynamic data masking (DDM) doesn't aim to prevent database users from connecting directly to the database and running exhaustive queries that expose pieces of the sensitive data."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://docs.oracle.com/en/database/oracle/oracle-database/19/asoag/security-considerations-for-using-oracle-data-redaction.html" rel="noopener noreferrer"&gt;Oracle's security guide&lt;/a&gt; says the same thing:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Oracle Data Redaction is not intended to protect against attacks by regular and privileged database users who run ad hoc queries directly against the database."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The core issue is that &lt;code&gt;WHERE&lt;/code&gt; clauses always run against the real, unmasked values. Only the output the user sees gets masked. That gap is exactly what makes inference attacks possible, and the diagram below shows how they work:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqkbc739gybyvncr7ncjv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqkbc739gybyvncr7ncjv.png" alt="DDM for Developers" width="703" height="725"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To see this in practice, consider a salary column masked to show zero. You run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;salary&lt;/span&gt; 
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;employees&lt;/span&gt; 
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;salary&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;99999&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;salary&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;100001&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The result returns "Jane Doe" with her salary showing as 0. But you already know what that means: Jane earns $100,000. Take this further with a binary search approach, and just 20 queries are enough to pinpoint any value within a million-dollar range down to the exact dollar.&lt;/p&gt;

&lt;p&gt;String columns leak through character-by-character extraction:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- Find first character&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;customers&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;email&lt;/span&gt; &lt;span class="k"&gt;LIKE&lt;/span&gt; &lt;span class="s1"&gt;'a%'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  &lt;span class="c1"&gt;-- No results&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;customers&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;email&lt;/span&gt; &lt;span class="k"&gt;LIKE&lt;/span&gt; &lt;span class="s1"&gt;'b%'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  &lt;span class="c1"&gt;-- No results&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;customers&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;email&lt;/span&gt; &lt;span class="k"&gt;LIKE&lt;/span&gt; &lt;span class="s1"&gt;'j%'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  &lt;span class="c1"&gt;-- Returns "Customer 1"&lt;/span&gt;

&lt;span class="c1"&gt;-- Find second character&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;customers&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;email&lt;/span&gt; &lt;span class="k"&gt;LIKE&lt;/span&gt; &lt;span class="s1"&gt;'ja%'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;-- No results&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;customers&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;email&lt;/span&gt; &lt;span class="k"&gt;LIKE&lt;/span&gt; &lt;span class="s1"&gt;'jo%'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;-- Returns "Customer 1"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A 24-character email requires roughly 6,000 iterations, and scripts can easily automate this.&lt;/p&gt;

&lt;p&gt;Aggregate functions disclose data regardless of masking:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- Both return actual sum, even though individual salaries show as 0&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;SUM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;salary&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;employees&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;department&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'Engineering'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;AVG&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;salary&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;employees&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;job_title&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'Senior Engineer'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;ORDER BY reveals relative rankings even when values display as masked. Someone with the highest salary appears first in &lt;code&gt;ORDER BY salary DESC&lt;/code&gt; regardless of what the salary column shows.&lt;/p&gt;

&lt;p&gt;This isn't a bug, it's a documented design decision. DDM is built to prevent accidental exposure in controlled, read-only contexts. It was never meant to protect against users who can query the database directly. And since developers write and run SQL queries against database tables, DDM simply can't protect sensitive data from them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Beyond DDM: Copy-on-Write Branching
&lt;/h2&gt;

&lt;p&gt;Copy-on-write branching is a storage technique that creates instant snapshots of your database. Instead of duplicating the entire dataset upfront, it only copies data when it's actually modified. This solves the staleness problem by making a "static" copy available instantly. Tools like &lt;a href="https://xata.io/postgres-branching" rel="noopener noreferrer"&gt;Xata&lt;/a&gt; implement this at the database level, giving you the security benefits of static masking with the speed that makes developers actually use it.&lt;/p&gt;

&lt;p&gt;The diagram below shows this approach:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftutkfkcrislaedgohdh8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftutkfkcrislaedgohdh8.png" alt="Copy-on-Write Branching" width="641" height="678"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here's how it works: &lt;a href="https://xata.io/documentation/platform/branch" rel="noopener noreferrer"&gt;Creating a branch&lt;/a&gt; instantiates a logical copy that initially shares all physical data pages with its parent. No data moves. A 200GB database branch creates in 30 seconds regardless of size. When you modify data in the branch, only changed pages consume additional storage.&lt;/p&gt;

&lt;p&gt;A secure development workflow looks like this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create a "golden" parent branch from production&lt;/li&gt;
&lt;li&gt;Apply static masking to this parent (happens once)&lt;/li&gt;
&lt;li&gt;Create child branches from the masked parent for developers or CI/CD&lt;/li&gt;
&lt;li&gt;Developers get full read/write access to realistic data&lt;/li&gt;
&lt;li&gt;Delete branches when done, storage reclaims immediately&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Developers get to work with actual production data patterns. Foreign keys work. Query performance matches production. Edge cases surface during testing instead of production incidents. But no unmasked sensitive data exists anywhere in the non-production environment.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The security gain:&lt;/strong&gt; Inference attacks fail with this approach because sensitive data is physically absent. No cleartext values exist to infer through WHERE clauses, JOINs, or backup files. That's a meaningful improvement over DDM, where the database always contains unmasked data regardless of access controls.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://xata.io/documentation/core-concepts/anonymization" rel="noopener noreferrer"&gt;Xata's data anonymization&lt;/a&gt; integrates directly into this branching workflow:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Create anonymized branch from production&lt;/span&gt;
xata branch create dev-branch &lt;span class="nt"&gt;--from&lt;/span&gt; main &lt;span class="nt"&gt;--anonymize&lt;/span&gt;

&lt;span class="c"&gt;# Branch ready in seconds with:&lt;/span&gt;
&lt;span class="c"&gt;# - All PII masked deterministically (referential integrity preserved)&lt;/span&gt;
&lt;span class="c"&gt;# - Realistic data distributions maintained&lt;/span&gt;
&lt;span class="c"&gt;# - Full read/write access for testing&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Where this approach proves most useful
&lt;/h3&gt;

&lt;p&gt;Let's say a production bug is impacting three specific customers. With DDM, handing developers access to investigate isn't really a safe option. But with copy-on-write branches, you can spin up an anonymized copy with just the relevant records in about 30 seconds. Developers can dig into real, realistic data, and once they're done, the branch is simply deleted. Production stays off-limits and no PII ever gets exposed.&lt;/p&gt;

&lt;p&gt;Where traditional static masking used to mean waiting through 4-hour ETL jobs, &lt;a href="https://xata.io/blog/branching-benefits-git-databases" rel="noopener noreferrer"&gt;modern copy-on-write branching&lt;/a&gt; gets you a fully masked environment in half a minute. You get the security guarantees of physically transforming the data, at a speed that developers will actually enjoy working with.&lt;/p&gt;

&lt;h2&gt;
  
  
  Best Practices for Implementation
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Step 1: Identify your PII
&lt;/h3&gt;

&lt;p&gt;Start by cataloging every column that holds sensitive data. This includes the obvious ones like names, emails, phone numbers, addresses, payment details, and health records, but also any regulatory-defined identifiers. Don't overlook derived fields either: usernames sometimes embed real names, and session logs can contain PII buried in URLs.&lt;/p&gt;

&lt;p&gt;For a comprehensive definition of what counts as PII, &lt;a href="https://gdpr-info.eu/" rel="noopener noreferrer"&gt;GDPR&lt;/a&gt; is a good reference. It covers genetic data, biometric data, location data, and online identifiers like IP addresses and cookies. The &lt;a href="https://oag.ca.gov/privacy/ccpa" rel="noopener noreferrer"&gt;CCPA&lt;/a&gt; goes further, extending coverage to commercial information and internet activity.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 2: Define roles and access patterns
&lt;/h3&gt;

&lt;p&gt;Think through which users actually need to see which data. Support agents might need the last four digits of a credit card for verification, but never the full number. Analysts need aggregated statistics, not individual customer records. Developers need data that looks and behaves realistically, but not real customer information.&lt;/p&gt;

&lt;p&gt;A useful way to think about this: separate your read-only operational users like support staff and analysts from your query-capable users like developers and data scientists. DDM can work reasonably well for the first group. For the second group, it's not a safe option.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 3: Choose the right tool
&lt;/h3&gt;

&lt;p&gt;The flowchart below guides how to effectively protect sensitive data with static or dynamic masking:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fns8ml2i7bnq9na569cen.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fns8ml2i7bnq9na569cen.png" alt="Choose the right tool" width="480" height="1121"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Use DDM&lt;/strong&gt; for production operational access when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Users only interact with data through controlled applications&lt;/li&gt;
&lt;li&gt;There's no ability to run ad hoc queries&lt;/li&gt;
&lt;li&gt;Read-only access is all that's needed&lt;/li&gt;
&lt;li&gt;You can audit every data access request&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Use &lt;a href="https://xata.io/postgres-data-masking" rel="noopener noreferrer"&gt;anonymized branches&lt;/a&gt;&lt;/strong&gt; for development when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Developers need to write and run their own queries&lt;/li&gt;
&lt;li&gt;Write access is required&lt;/li&gt;
&lt;li&gt;Integration testing needs referential integrity to hold up&lt;/li&gt;
&lt;li&gt;You want to remove any possibility of inference attacks&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can also use &lt;em&gt;both&lt;/em&gt; together. DDM handles protection on the production side for operational users, while &lt;a href="https://xata.io/documentation/core-concepts/branching" rel="noopener noreferrer"&gt;copy-on-write branches&lt;/a&gt; give developers and staging environments a safe place to work. These two masking approaches solve two different problems.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 4: Set up security monitoring
&lt;/h3&gt;

&lt;p&gt;Keep an eye on data access patterns so you can catch inference attacks early. Some things worth watching for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Repeated similar queries where the &lt;code&gt;WHERE&lt;/code&gt; clause value keeps incrementing&lt;/li&gt;
&lt;li&gt;A high volume of queries that each return just a single row&lt;/li&gt;
&lt;li&gt;Aggregate queries that target unusually small subsets of data&lt;/li&gt;
&lt;li&gt;Pattern matching queries with systematic character variations&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://www.pgaudit.org/" rel="noopener noreferrer"&gt;PostgreSQL's audit extensions&lt;/a&gt; and AWS RDS audit logs can both help you capture and analyze these patterns.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Dynamic Data Masking (DDM) has a clear and specific purpose: keeping sensitive data from being accidentally exposed in read-only, application-controlled production scenarios. So it is ideal for customer support dashboards, operational reporting, or any context where users interact with data through fixed interfaces and can't run their own queries.&lt;/p&gt;

&lt;p&gt;Where it falls short is with developers. Because developers write and run queries directly, WHERE clauses, aggregations, and inference attacks can pull out masked values no matter how DDM is configured. Development environments need write access and query freedom, which means DDM is a poor fit there by design.&lt;/p&gt;

&lt;p&gt;Real security for development workflows means giving developers safe data to work with, not just hiding unsafe data behind a filter. Storage-layer approaches that combine copy-on-write branching with static masking give you both: the data is physically transformed so it's genuinely secure, and masked environments are ready in about 30 seconds. Traditional static masking had the security part right but killed developer velocity with hours-long ETL jobs. Modern branching tools solve both sides of that tradeoff, and if your team is still leaning on DDM to protect development environments, they're worth a serious look.&lt;/p&gt;

</description>
      <category>data</category>
      <category>dataengineering</category>
    </item>
    <item>
      <title>Automate DNS Management with the name.com API</title>
      <dc:creator>Jakkie Koekemoer</dc:creator>
      <pubDate>Thu, 05 Mar 2026 17:38:04 +0000</pubDate>
      <link>https://forem.com/jakkie_koekemoer/automate-dns-management-with-the-namecom-api-35g</link>
      <guid>https://forem.com/jakkie_koekemoer/automate-dns-management-with-the-namecom-api-35g</guid>
      <description>&lt;p&gt;Infrastructure as Code has changed how engineering teams manage servers, databases, and networks. But DNS is still a manual bottleneck for a lot of teams. You log into a dashboard, click through forms, and hope you don't fat-finger a record mid-deployment.&lt;/p&gt;

&lt;p&gt;That works fine when you're managing a handful of domains. It stops working when you're juggling dozens of domains, spinning up ephemeral environments, or coordinating deployments across multiple services.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://www.name.com/nameapi" rel="noopener noreferrer"&gt;name.com API&lt;/a&gt; is the fix. It's a REST API that lets you programmatically search, register, and manage your DNS records. Unlike registrars that bolt on API access as an enterprise upsell or an afterthought, name.com provides &lt;a href="https://docs.name.com/docs/api-overview" rel="noopener noreferrer"&gt;API access as a core feature&lt;/a&gt;, with standard HTTP methods, JSON responses, and real documentation.&lt;/p&gt;

&lt;p&gt;By the end, you'll have the building blocks for DNS automation that can be triggered directly from your CI/CD pipeline.&lt;/p&gt;

&lt;h2&gt;
  
  
  Authentication and Security
&lt;/h2&gt;

&lt;p&gt;API access starts in your &lt;a href="https://www.name.com/account/create" rel="noopener noreferrer"&gt;name.com dashboard&lt;/a&gt;. Navigate to API, then &lt;a href="https://www.name.com/account/settings/api" rel="noopener noreferrer"&gt;API Token Management&lt;/a&gt;. You get two environments: production (&lt;code&gt;api.name.com&lt;/code&gt;) and development/test (&lt;code&gt;api.dev.name.com&lt;/code&gt;). The test environment uses your username with &lt;code&gt;-test&lt;/code&gt; appended and a separate token. This lets you safely test DNS changes without touching live domains.&lt;/p&gt;

&lt;p&gt;The API uses &lt;a href="https://docs.name.com/docs/authentication" rel="noopener noreferrer"&gt;HTTP Basic Auth&lt;/a&gt;. Your username and token combine into a base64-encoded header. Here's how to construct it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;base64&lt;/span&gt;

&lt;span class="c1"&gt;# Environment variables for credentials
&lt;/span&gt;&lt;span class="n"&gt;username&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;NAMECOM_USERNAME&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;token&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;NAMECOM_TOKEN&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Construct the Basic Auth header
&lt;/span&gt;&lt;span class="n"&gt;credentials&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;username&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;:&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;encoded_credentials&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;base64&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;b64encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;credentials&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="nf"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;auth_header&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Basic &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;encoded_credentials&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="c1"&gt;# Use in requests
&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Authorization&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;auth_header&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Content-Type&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;application/json&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Always include this header in your API requests.&lt;/p&gt;

&lt;p&gt;Never hardcode credentials in scripts. Use environment variables or a secrets manager like AWS Secrets Manager, HashiCorp Vault, or your CI/CD platform's native secrets feature. A &lt;code&gt;.env&lt;/code&gt; file works for local development, but add it to &lt;code&gt;.gitignore&lt;/code&gt; immediately.&lt;/p&gt;

&lt;p&gt;For team environments, rotate tokens on a regular schedule. Name.com lets you generate multiple tokens, so you can implement token-per-service or token-per-developer patterns. If a token gets compromised, you can revoke it without disrupting other services.&lt;/p&gt;

&lt;p&gt;One gotcha worth knowing: two-factor authentication on your name.com account blocks API access. If you need 2FA for security compliance, create a dedicated API-only account with restricted permissions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Understanding API Constraints
&lt;/h2&gt;

&lt;p&gt;The name.com API has a &lt;a href="https://docs.name.com/api/v1/overview#rate-limits" rel="noopener noreferrer"&gt;rate limit of 3,000 requests per hour&lt;/a&gt;. For most automation tasks, this is plenty. Updating records for a dozen domains or running scheduled zone backups fits comfortably within it.&lt;/p&gt;

&lt;p&gt;Problems can show up during bulk operations. If you're migrating hundreds of records or running parallel updates across multiple environments, you'll start hitting &lt;code&gt;429 Too Many Requests&lt;/code&gt; responses. The solution is exponential backoff:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;make_request_with_backoff&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;method&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;GET&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;max_retries&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
    Makes an API request with exponential backoff on rate limit errors.

    Args:
        url: API endpoint URL
        headers: Request headers including auth
        method: HTTP method (GET, POST, PUT, DELETE)
        data: JSON payload for POST/PUT requests
        max_retries: Maximum number of retry attempts

    Returns:
        Response object or None if all retries failed
    &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;attempt&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;max_retries&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;method&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;GET&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="n"&gt;method&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;POST&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="n"&gt;method&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;PUT&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;put&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="n"&gt;method&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;DELETE&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;delete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status_code&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;429&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;wait_time&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt; &lt;span class="n"&gt;attempt&lt;/span&gt;
            &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Rate limited. Waiting &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;wait_time&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; seconds before retry &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;attempt&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;max_retries&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;wait_time&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;continue&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;

    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Failed after &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;max_retries&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; attempts&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Beyond rate limits, you'll also encounter standard HTTP status codes. &lt;code&gt;401 Unauthorized&lt;/code&gt; means your credentials are wrong, so check your username, token, and base64 encoding. &lt;code&gt;403 Forbidden&lt;/code&gt; usually means you're missing the &lt;code&gt;Content-Type: application/json&lt;/code&gt; header on POST or PUT requests. &lt;code&gt;404 Not Found&lt;/code&gt; means the domain or record doesn't exist. In sandbox environments, you need to register domains before you can manage their DNS.&lt;/p&gt;

&lt;h2&gt;
  
  
  Retrieving Data and Handling Pagination
&lt;/h2&gt;

&lt;p&gt;The name.com API doesn't have a "Download Zone File" button. You reconstruct the zone view programmatically by fetching all records through the &lt;a href="https://docs.name.com/api/v1/reference/dns/list-records#list-records" rel="noopener noreferrer"&gt;List Records endpoint&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The catch is pagination. Large zones with hundreds of records don't return in a single response. The API uses &lt;code&gt;page&lt;/code&gt; and &lt;code&gt;perPage&lt;/code&gt; parameters, with a default of 1,000 records per page. That handles most domains, but for larger zones or when you want smaller batches, adjust &lt;code&gt;perPage&lt;/code&gt; accordingly.&lt;/p&gt;

&lt;p&gt;Here's the logic for reconstructing a complete zone:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_all_dns_records&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;domain&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
    Retrieves all DNS records for a domain by handling pagination.

    Args:
        domain: Domain name (e.g., &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;draftexample.work&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;)
        headers: Request headers with authentication

    Returns:
        List of all DNS records for the domain
    &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;base_url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://api.name.com/core/v1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="n"&gt;all_records&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
    &lt;span class="n"&gt;page&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="n"&gt;per_page&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;

    &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;base_url&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/domains/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;domain&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/records?page=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;&amp;amp;perPage=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;per_page&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status_code&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Error fetching records: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status_code&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;break&lt;/span&gt;

        &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;records&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;records&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[])&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;records&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;break&lt;/span&gt;

        &lt;span class="n"&gt;all_records&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;extend&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;records&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;records&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;per_page&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;break&lt;/span&gt;

        &lt;span class="n"&gt;page&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Fetched page &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;records&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; records&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Total records retrieved: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;all_records&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;all_records&lt;/span&gt;

&lt;span class="c1"&gt;# Example usage
&lt;/span&gt;&lt;span class="n"&gt;domain&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;draftexample.work&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;records&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get_all_dns_records&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;domain&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Save to local "zone state" for analysis
&lt;/span&gt;&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;domain&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;_zone_backup.json&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;w&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dump&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;records&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;indent&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each record includes an &lt;code&gt;id&lt;/code&gt; field. That's your handle for updates and deletions. The response structure looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;12345&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"domainName"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"draftexample.work"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"host"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"test"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"fqdn"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"test.draftexample.work"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"A"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"answer"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"127.0.0.1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"ttl"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;300&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Store these IDs when building automation. A common pattern is maintaining a local state file that maps logical names, like "production-web-server," to record IDs. This lets you update records by intent rather than hunting through API responses every time.&lt;/p&gt;

&lt;h2&gt;
  
  
  Managing State with CRUD Operations
&lt;/h2&gt;

&lt;p&gt;DNS automation comes down to four operations: &lt;a href="https://docs.name.com/docs/api-reference/dns/create-record" rel="noopener noreferrer"&gt;creating records&lt;/a&gt;, &lt;a href="https://docs.name.com/docs/api-reference/dns/get-record" rel="noopener noreferrer"&gt;reading records&lt;/a&gt; (covered above), &lt;a href="https://docs.name.com/docs/api-reference/dns/update-record" rel="noopener noreferrer"&gt;updating records&lt;/a&gt;, and &lt;a href="https://docs.name.com/docs/api-reference/dns/delete-record" rel="noopener noreferrer"&gt;deleting records&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Creating Records
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;POST /core/v1/domains/{domain}/records&lt;/code&gt; endpoint creates new records. You provide the &lt;a href="https://www.name.com/support/articles/205516858-understanding-dns-record-types" rel="noopener noreferrer"&gt;record type&lt;/a&gt;, hostname, and answer. The API validates record-type-specific rules:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;create_dns_record&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;domain&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;record_type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;host&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;answer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ttl&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;300&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
    Creates a new DNS record.

    Args:
        domain: Domain name
        record_type: Type of record (A, CNAME, TXT, MX, etc.)
        host: Hostname (use &lt;/span&gt;&lt;span class="sh"&gt;''&lt;/span&gt;&lt;span class="s"&gt; for apex domain)
        answer: Record value (IP for A, hostname for CNAME, etc.)
        ttl: Time to live in seconds (minimum 300)
        headers: Request headers with authentication

    Returns:
        Created record data or None on failure
    &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://api.name.com/core/v1/domains/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;domain&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/records&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

    &lt;span class="n"&gt;payload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;host&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;host&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;record_type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;answer&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;answer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ttl&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ttl&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;record_type&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;MX&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;priority&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;

    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status_code&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;record&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Created &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;record_type&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; record: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;host&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;.&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;domain&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; -&amp;gt; &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;answer&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;record&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Failed to create record: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status_code&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;

&lt;span class="c1"&gt;# Example: Create an A record for a staging environment
&lt;/span&gt;&lt;span class="nf"&gt;create_dns_record&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;domain&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;draftexample.work&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;record_type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;A&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;host&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;staging&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;answer&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;192.168.2.50&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;ttl&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;300&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Validation matters here. An &lt;a href="https://www.name.com/support/articles/115004893508-adding-an-a-record" rel="noopener noreferrer"&gt;A record&lt;/a&gt; needs a valid IPv4 address. &lt;a href="https://www.name.com/support/articles/115004895548-adding-a-cname-record" rel="noopener noreferrer"&gt;CNAME records&lt;/a&gt; can't coexist with other record types at the same hostname. &lt;a href="https://www.name.com/support/articles/115004972547-adding-a-txt-record" rel="noopener noreferrer"&gt;TXT records&lt;/a&gt; need proper quoting for spaces. The API returns &lt;code&gt;400 Bad Request&lt;/code&gt; with details when validation fails.&lt;/p&gt;

&lt;p&gt;You can verify the record was created in your name.com dashboard:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F05f8wx45m45d6pke53n3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F05f8wx45m45d6pke53n3.png" alt="Record created successfully" width="800" height="255"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Updating Records
&lt;/h3&gt;

&lt;p&gt;Updates use &lt;code&gt;PUT /core/v1/domains/{domain}/records/{record_id}&lt;/code&gt;. You need the record ID from a previous GET request:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;update_dns_record&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;domain&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;record_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;record_type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;host&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;answer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ttl&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;300&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
    Updates an existing DNS record.

    Args:
        domain: Domain name
        record_id: ID of the record to update
        record_type: Type of record
        host: Hostname
        answer: New record value
        ttl: Time to live in seconds
        headers: Request headers with authentication

    Returns:
        Updated record data or None on failure
    &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://api.name.com/core/v1/domains/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;domain&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/records/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;record_id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

    &lt;span class="n"&gt;payload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;host&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;host&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;record_type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;answer&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;answer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ttl&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ttl&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;put&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status_code&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Updated record &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;record_id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;host&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;.&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;domain&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; -&amp;gt; &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;answer&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Failed to update record: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status_code&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;

&lt;span class="c1"&gt;# Example: Update production IP after server migration
&lt;/span&gt;&lt;span class="nf"&gt;update_dns_record&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;domain&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;draftexample.work&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;record_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;123456&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;record_type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;A&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;host&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;staging&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;answer&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;192.168.2.100&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;# New IP
&lt;/span&gt;    &lt;span class="n"&gt;ttl&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;300&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Updates are atomic at the record level. The API replaces the entire record, not individual fields. This prevents partial updates that could create invalid states.&lt;/p&gt;

&lt;p&gt;Once you've run &lt;code&gt;update_dns_record&lt;/code&gt;, the change shows up in the name.com dashboard:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyiymi6mq2a3c840v9b8n.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyiymi6mq2a3c840v9b8n.png" alt="Record updated successfully" width="800" height="246"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Deleting Records
&lt;/h3&gt;

&lt;p&gt;Deletion uses &lt;code&gt;DELETE /core/v1/domains/{domain}/records/{record_id}&lt;/code&gt;. It's worth adding safety checks to prevent accidental deletion of critical infrastructure:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;delete_dns_record&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;domain&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;record_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;require_confirmation&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
    Deletes a DNS record with safety checks.

    Args:
        domain: Domain name
        record_id: ID of the record to delete
        headers: Request headers with authentication
        require_confirmation: If True, requires explicit confirmation

    Returns:
        True if successful, False otherwise
    &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://api.name.com/core/v1/domains/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;domain&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/records/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;record_id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status_code&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;record&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;About to delete: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;type&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;fqdn&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; -&amp;gt; &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;answer&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="n"&gt;critical_hosts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;www&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;mail&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;mx&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;host&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;critical_hosts&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;require_confirmation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;confirm&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;input&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;This is a critical record. Type &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;DELETE&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt; to confirm: &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;confirm&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;DELETE&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Deletion cancelled&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;

    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;delete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status_code&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;204&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Deleted record &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;record_id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Failed to delete record: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status_code&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;

&lt;span class="c1"&gt;# Example: Clean up old staging environment
&lt;/span&gt;&lt;span class="nf"&gt;delete_dns_record&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;domain&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;draftexample.work&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;record_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;123456&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;require_confirmation&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;  &lt;span class="c1"&gt;# Automated cleanup
&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For bulk deletions, track which records were successfully deleted. If the operation fails partway through, you can resume without duplicating deletions.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Strategic Use Case: Ephemeral Environments
&lt;/h2&gt;

&lt;p&gt;DNS automation unlocks workflows that simply aren't practical with manual management. Ephemeral environments are a good example.&lt;/p&gt;

&lt;p&gt;There are times when you spin up a full environment, like &lt;code&gt;dev&lt;/code&gt; or &lt;code&gt;staging&lt;/code&gt;, using CI/CD pipelines. DNS entries are usually a part of that setup that you end up doing by hand. With the name.com API, you can automate that step entirely.&lt;/p&gt;

&lt;p&gt;Feature branch deployments create temporary infrastructure, and each pull request can get its own subdomain:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;create_ephemeral_environment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;domain&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;branch_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;server_ip&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
    Creates DNS records for a temporary feature branch environment.

    Args:
        domain: Base domain
        branch_name: Git branch name (sanitized for DNS)
        server_ip: IP address of the ephemeral server
        headers: Request headers with authentication

    Returns:
        Created record or None
    &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;safe_branch&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;branch_name&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;_&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;-&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;/&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;-&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;host&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;feature-&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;safe_branch&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;create_dns_record&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;domain&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;domain&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;record_type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;A&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;host&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;host&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;answer&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;server_ip&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;ttl&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;300&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;# Short TTL for quick cleanup
&lt;/span&gt;        &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# In your CI/CD pipeline
&lt;/span&gt;&lt;span class="n"&gt;branch&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;GITHUB_BRANCH&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# e.g., 'user-auth-refactor'
&lt;/span&gt;&lt;span class="n"&gt;server_ip&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;provision_server&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;  &lt;span class="c1"&gt;# Your infrastructure provisioning
&lt;/span&gt;
&lt;span class="n"&gt;record&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;create_ephemeral_environment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;domain&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;draftexample.work&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;branch_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;branch&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;server_ip&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;server_ip&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Environment available at: https://feature-&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;branch&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;.example.com&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Keep in mind that the &lt;code&gt;provision_server&lt;/code&gt; function (or similar function) is normally run on your CI/CD infrastructure and should already be in place for this code to work.&lt;/p&gt;

&lt;p&gt;If that runs successfully from your CI/CD environment, you'll end up with a record that looks like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvcpj8eyp8jwh0erby0hd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvcpj8eyp8jwh0erby0hd.png" alt="Created from CI/CD environment" width="800" height="251"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>dns</category>
      <category>api</category>
    </item>
    <item>
      <title>Why Developers Feel Unproductive Even When They're Shipping</title>
      <dc:creator>Jakkie Koekemoer</dc:creator>
      <pubDate>Wed, 04 Mar 2026 16:58:36 +0000</pubDate>
      <link>https://forem.com/jakkie_koekemoer/why-developers-feel-unproductive-even-when-theyre-shipping-47ek</link>
      <guid>https://forem.com/jakkie_koekemoer/why-developers-feel-unproductive-even-when-theyre-shipping-47ek</guid>
      <description>&lt;p&gt;I've talked to a lot of engineering managers who can't figure out why their developers seem frustrated. The sprint metrics look fine. PRs are getting merged. Tickets are moving. But morale is low, and the team keeps saying they feel like they're not getting anything done.&lt;/p&gt;

&lt;p&gt;After digging into it, I've found the problem is almost never about output. It's about the conditions under which that output gets produced.&lt;/p&gt;

&lt;p&gt;Most developers aren't struggling because they're lazy or distracted. They're struggling because their calendars make sustained focus structurally impossible. You can ship code in 20-minute windows between meetings all day long and still end the day feeling like you accomplished nothing, because the kind of work that actually feels meaningful requires more than 20 minutes to do.&lt;/p&gt;

&lt;p&gt;There's a name for this: productivity dysmorphia. It's the gap between what you objectively produced and what you subjectively feel like you produced. And the research explains exactly why that gap exists.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Fragmented Days Feel Empty
&lt;/h2&gt;

&lt;p&gt;When you switch tasks, your brain doesn't just pick up where it left off. A &lt;a href="https://www.ics.uci.edu/~gmark/chi08-mark.pdf" rel="noopener noreferrer"&gt;study from UC Irvine&lt;/a&gt; found it takes an average of 23 minutes to fully regain focus after an interruption. That's not 23 seconds. It's 23 minutes of recovery time before you're thinking at the depth you were before.&lt;/p&gt;

&lt;p&gt;That number becomes a real problem when you look at how most engineering calendars are structured. If you have meetings at 9:30, 11:00, 1:00, and 3:00, you don't have four gaps of free time. You have four windows that are almost entirely consumed by context-switching overhead. By the time your brain is ready to do something hard, the next meeting is already close enough to pull your attention toward it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fm70sbuk75kqxbsq10es0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fm70sbuk75kqxbsq10es0.png" alt="Why Fragmented Days Feel Empty" width="800" height="87"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;A 30-minute gap between meetings yields about 7 minutes of usable focus. A 2-hour block yields nearly 100. The difference isn't linear; it's structural.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Psychologist Sophie Leroy's research on &lt;a href="https://www.sciencedirect.com/science/article/pii/S0749597809000399" rel="noopener noreferrer"&gt;attention residue&lt;/a&gt; makes this worse. Even after you physically move to a new task, part of your cognitive bandwidth stays stuck on the previous one. You leave a 2 PM standup and open your editor, but your brain is still processing what was said in the meeting. You're typing, but you're not thinking.&lt;/p&gt;

&lt;p&gt;This is why three hotfix PRs feel less satisfying than one complex feature, even if the hotfixes took longer in total. The hotfixes get done in short bursts of reactive work. The complex feature requires the kind of deep, sustained focus that Csikszentmihalyi called &lt;a href="https://www.researchgate.net/publication/224927532_Flow_The_Psychology_of_Optimal_Experience" rel="noopener noreferrer"&gt;Flow&lt;/a&gt;: full immersion in a hard problem, where time disappears and progress feels real. A fragmented calendar makes Flow nearly impossible to reach. So developers ship things and still feel empty, because they never got to do the work that actually satisfies them.&lt;/p&gt;

&lt;p&gt;&lt;a href="http://www.paulgraham.com/makersschedule.html" rel="noopener noreferrer"&gt;Paul Graham described the structural version of this problem&lt;/a&gt; as the maker's schedule versus the manager's schedule. Managers think in one-hour blocks, so a single meeting just fills a slot. For a developer, the same meeting can ruin an entire afternoon by eliminating any realistic chance of reaching deep focus before or after it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem With Introspection
&lt;/h2&gt;

&lt;p&gt;Here's what makes productivity dysmorphia hard to diagnose: asking yourself how your day went doesn't work.&lt;/p&gt;

&lt;p&gt;Ask a developer at 5 PM how many real focus blocks they had that day. They'll probably say "a few." The actual number, meaning uninterrupted stretches long enough to load context and do hard work, is often zero. &lt;a href="https://doi.org/10.3758/BF03209393" rel="noopener noreferrer"&gt;Research on retrospective time perception&lt;/a&gt; shows that we consistently compress the memory of fragmented work and expand the memory of focused work. What felt like a productive day was actually dozens of short reactive tasks stitched together. The brain smooths it over. The fatigue is real, but the memory lies.&lt;/p&gt;

&lt;p&gt;This is where data becomes genuinely useful, not as a productivity metric, but as a reality check.&lt;/p&gt;

&lt;p&gt;GitHub's &lt;a href="https://github.blog/news-insights/research/octoverse-spotlight-good-day-project/" rel="noopener noreferrer"&gt;Good Day Project&lt;/a&gt; studied 40 developers over two weeks and found that with minimal interruptions, developers had an &lt;strong&gt;82% chance of having a good day&lt;/strong&gt;. When interruptions dominated their day, that dropped to &lt;strong&gt;7%&lt;/strong&gt;. They also found that the developers who shipped the most pull requests didn't necessarily have the best days, because being constantly pulled out of focus to handle PRs appeared to cancel out the satisfaction of completing them.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flmuyq68x16qwt8z8xd22.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flmuyq68x16qwt8z8xd22.png" alt="GitHub Good Day Project: Interruptions vs. Chance of a Good Day" width="700" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Source: GitHub Octoverse Good Day Project, 2021. N=40 developers over two weeks.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;That last finding is the clearest illustration of productivity dysmorphia I've seen in the research. You can be shipping constantly and still feel like you're not getting anywhere.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.span.app" rel="noopener noreferrer"&gt;Span&lt;/a&gt; addresses this at the team level. It connects to your codebase and engineering toolchain and automatically categorizes where developer time is actually going across meetings, deep work, maintenance, and context switching, without requiring manual tracking. When an engineering manager can see that their team logged 200 hours last week but the majority was consumed by meetings and reactive work, the conversation changes. The problem stops being "we need to work harder" and starts being "we need to protect more time for actual engineering work."&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqo44oyaa326tv8w4kx0f.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqo44oyaa326tv8w4kx0f.png" alt="Span dashboard showing Focus Time at 20.5 hours per week (up 12%) alongside a Meeting Time Breakdown by recurrence type" width="800" height="419"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That shift in framing matters. When a team attributes the hollow feeling to personal failure, it's demoralizing and hard to fix. When they can see it in a dashboard and attribute it to a structural problem, it becomes something they can actually address.&lt;/p&gt;

&lt;h2&gt;
  
  
  What to Do About It
&lt;/h2&gt;

&lt;p&gt;None of the fixes here are complicated. The hard part is treating them as real constraints rather than nice-to-haves.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Protect at least one two-hour block per developer per day.&lt;/strong&gt; This is the minimum viable unit of deep work. Thirty or forty-five minutes isn't enough to load context on a hard problem and make meaningful progress. By the time a developer has pulled the branch, reviewed the ticket, traced the relevant code, and formed a clear mental model of what needs to happen, the window is already closing. If you look at your team's calendars and there's no consistent uninterrupted two-hour stretch, you've scheduled a week of reactive work and called it an engineering sprint.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Batch reactive work into fixed windows.&lt;/strong&gt; Code reviews, Slack replies, and PR comments are real work, but they're interruptive by nature. Grouping them into two fixed windows per day, say mid-morning and late afternoon, keeps them from bleeding into focus time throughout the day. This is a scheduling convention, not a policy. It requires no tools and no approval.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Establish explicit response-time expectations for Slack.&lt;/strong&gt; The biggest source of interruptions for most engineering teams isn't meetings. It's the assumption that Slack messages need a near-immediate reply. That assumption is almost never written down anywhere, but it shapes behavior constantly. Teams that set a clear norm, like "non-urgent messages get a response within two hours," consistently report less anxiety without any measurable drop in communication quality. One team conversation can change the default permanently.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Use data to make the problem visible to leadership.&lt;/strong&gt; Individual developers can protect their own calendars to some extent, but the real leverage is at the team and org level. When a manager can show in a dashboard that 60% of engineering time is going to meetings and reactive work, it's a different conversation than asking people to "block more focus time." Span's engineering time categorization is useful here because it provides the kind of objective, code-linked data that's hard to argue with.&lt;/p&gt;

&lt;p&gt;That said, none of these changes require a reorganization or executive buy-in to start. They require that someone on the team decides to treat focus time as a resource worth defending rather than the default thing that gets scheduled over.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;Productivity dysmorphia is a predictable outcome of how most engineering orgs are structured, not a sign that developers aren't working hard enough. When the calendar makes deep work structurally impossible, people feel unproductive because they are, not in terms of output, but in terms of the work that actually registers as meaningful.&lt;/p&gt;

&lt;p&gt;The fix isn't motivational. It's architectural. Protect the blocks, batch the reactive work, and make the time distribution visible with data. The developers on your team probably already know something is wrong. The data just gives you a way to show it to everyone else.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Want to understand where your engineering team's time is actually going? &lt;a href="https://www.span.app" rel="noopener noreferrer"&gt;Span&lt;/a&gt; gives engineering leaders an automated view of how developer time is distributed across deep work, meetings, and reactive tasks, without the manual overhead.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>productivity</category>
      <category>ai</category>
    </item>
    <item>
      <title>AI is Killing the "Junior" Role. Here is How We Save It.</title>
      <dc:creator>Jakkie Koekemoer</dc:creator>
      <pubDate>Wed, 25 Feb 2026 13:11:52 +0000</pubDate>
      <link>https://forem.com/jakkie_koekemoer/ai-is-killing-the-junior-role-here-is-how-we-save-it-33cc</link>
      <guid>https://forem.com/jakkie_koekemoer/ai-is-killing-the-junior-role-here-is-how-we-save-it-33cc</guid>
      <description>&lt;p&gt;The desk next to yours is empty. It used to belong to a junior developer. It's not coming back.&lt;/p&gt;

&lt;h2&gt;
  
  
  Introduction: The Empty Desk Next to You
&lt;/h2&gt;

&lt;p&gt;Look at your company's open engineering reqs. Chances are, you're hiring for Senior, Staff, or Principal engineers. The junior reqs? Frozen. Or quietly removed.&lt;/p&gt;

&lt;p&gt;This isn't anecdotal. &lt;a href="https://www.cnbc.com/2025/08/28/generative-ai-reshapes-us-job-market-stanford-study-shows-entry-level-young-workers.html" rel="noopener noreferrer"&gt;Stanford's Digital Economy Lab&lt;/a&gt; found that employment for the youngest software developers dropped 13% below its late-2022 peak by mid-2025, while older developers in the same fields remained stable. A &lt;a href="https://papers.ssrn.com/sol3/papers.cfm?abstract_id=5425555" rel="noopener noreferrer"&gt;Harvard study analyzing 62 million workers across 285,000 firms&lt;/a&gt; found that following GenAI adoption, junior employment declined sharply in adopting firms relative to non-adopters, while senior employment remained largely unchanged, driven by slower hiring rather than increased separations or promotions. &lt;a href="https://www.signalfire.com/blog/signalfire-state-of-talent-report-2025" rel="noopener noreferrer"&gt;SignalFire's 2025 State of Tech Talent Report&lt;/a&gt; found that new graduate hiring at major tech firms has fallen over 50% since 2019.&lt;/p&gt;

&lt;p&gt;The economic logic is straightforward. Why pay a junior engineer \$80K to write boilerplate when Claude Code does it for \$20 per month? The math is brutal, the incentive is real, and companies are acting on it.&lt;/p&gt;

&lt;p&gt;But this creates a problem that doesn't show up on any quarterly earnings call: we are eating our seed corn. If today's juniors don't get the reps, there will be nobody to become the seniors who clean up AI's mess in 2030. AWS CEO Matt Garman put it bluntly when he called the trend of cutting junior hiring &lt;a href="https://fortune.com/2025/12/16/aws-ceo-matt-garman-ai-displacing-junior-employees-dumbest-idea-amazon-layoffs/" rel="noopener noreferrer"&gt;"one of the dumbest things I've ever heard"&lt;/a&gt;, asking: "How's that going to work when ten years in the future you have no one that has learned anything?"&lt;/p&gt;

&lt;p&gt;That's the question this article is trying to answer.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Death of "Wax On, Wax Off"
&lt;/h2&gt;

&lt;p&gt;The &lt;a href="https://en.wikipedia.org/wiki/Dreyfus_model_of_skill_acquisition" rel="noopener noreferrer"&gt;Dreyfus Model of Skill Acquisition&lt;/a&gt; describes five stages of competence: Novice, Advanced Beginner, Competent, Proficient, Expert. The critical insight is that you can't skip stages. You progress by doing, failing, and building pattern recognition through repetition. In the Karate Kid, Mr. Miyagi doesn't explain the philosophy of karate on day one. He has Daniel wax a car, sand a floor, and paint a fence. The muscle memory comes first. The understanding follows.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fq7iy6uiykyzbmt8cp0e5.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fq7iy6uiykyzbmt8cp0e5.jpg" alt="Mr. Miyagi's " width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Mr. Miyagi's "wax on, wax off" method is the original onboarding framework: repetitive, concrete tasks that build muscle memory before the deeper principles are ever explained.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Juniors used to learn the same way. They wrote the unit tests. They built the CRUD endpoints. They handled the CSS tickets that nobody else wanted. The work was boring. That was the point. In doing repetitive, well-scoped tasks, they built the mental model of how a codebase holds together. They learned to read error messages. They developed the intuition for where bugs hide.&lt;/p&gt;

&lt;p&gt;AI has automated the gym.&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmermaid.ink%2Fimg%2Fpako%3AeNqFkVFPwjAQx79Kc7wy7MZWoA8ksBGDUV_0SedDw66wuLWk61AEvrtlA0I0xj5cer3f_Xv9dwcLnSFwWBqxXpHnJFXErclrCk-ocm3ITC1zhWhSeCOeNyZTV3rIM-8eN1hcV9vOaQPFDrqrf_a3RNwQiSPmyqJR5IY84ge5NSK7MG2s7LZAMiEyLwreCTImQtld6EIb3pFSXkPTExTSkc8Wf0DxCZKhCJh_hiil11BygnDAZOT_UmrjzE0_mRNRW10KixVBZc3WKxpPrKjeq8aunjfepzDV1mHE1GpZEYOl3qB76Z7EJ63_uAS67nvyDLg1NXahRFOKYwq7o0IKdoUlpsDdNkMp6sKmkKqDa1sL9aJ1ee40ul6ugEtRVC6r15mbPcmF-_vycmpQZWhiXSsLPAobDeA7-AQ-7LOeH9KIstEwiPrOQtgCDyLWc6aP_L7P_BEbROzQha_mVr9HGR0ENKBRnw4oZcPDNyOovmg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmermaid.ink%2Fimg%2Fpako%3AeNqFkVFPwjAQx79Kc7wy7MZWoA8ksBGDUV_0SedDw66wuLWk61AEvrtlA0I0xj5cer3f_Xv9dwcLnSFwWBqxXpHnJFXErclrCk-ocm3ITC1zhWhSeCOeNyZTV3rIM-8eN1hcV9vOaQPFDrqrf_a3RNwQiSPmyqJR5IY84ge5NSK7MG2s7LZAMiEyLwreCTImQtld6EIb3pFSXkPTExTSkc8Wf0DxCZKhCJh_hiil11BygnDAZOT_UmrjzE0_mRNRW10KixVBZc3WKxpPrKjeq8aunjfepzDV1mHE1GpZEYOl3qB76Z7EJ63_uAS67nvyDLg1NXahRFOKYwq7o0IKdoUlpsDdNkMp6sKmkKqDa1sL9aJ1ee40ul6ugEtRVC6r15mbPcmF-_vycmpQZWhiXSsLPAobDeA7-AQ-7LOeH9KIstEwiPrOQtgCDyLWc6aP_L7P_BEbROzQha_mVr9HGR0ENKBRnw4oZcPDNyOovmg" alt="AI automates entry-level engineering tasks" width="537" height="430"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The Missing Rung: AI has automated the entry-level tasks that juniors used to use as their training ground, leaving a gap that's impossible to jump.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The bottom rungs of the career ladder, the boilerplate code, the unit tests, the CRUD endpoints, have been removed. And we're now telling juniors to jump straight to the top.&lt;/p&gt;

&lt;p&gt;The result is what you might call the Hollow Senior: a developer with five years of "experience" that is really five years of prompting AI, accepting outputs, and shipping features without ever debugging a race condition, optimizing a slow query, or understanding why a seemingly simple database index choice can bring down a production system at scale. They have the title. They lack the intuition.&lt;/p&gt;

&lt;p&gt;The research backs this up. &lt;a href="https://www.aalto.fi/en/news/researchers-warn-that-skill-erosion-caused-by-ai-could-have-a-devastating-and-lasting-impact-on" rel="noopener noreferrer"&gt;Aalto University researchers&lt;/a&gt; studied a firm where automation dependence eroded core skills so thoroughly that when the system was removed, employees could no longer perform basic tasks. A &lt;a href="https://www.microsoft.com/en-us/research/publication/the-impact-of-generative-ai-on-critical-thinking-self-reported-reductions-in-cognitive-effort-and-confidence-effects-from-a-survey-of-knowledge-workers/" rel="noopener noreferrer"&gt;Microsoft and Carnegie Mellon study&lt;/a&gt; found that higher AI confidence directly correlated with reduced critical thinking. And a &lt;a href="https://metr.org/blog/2025-07-10-early-2025-ai-experienced-os-dev-study/" rel="noopener noreferrer"&gt;METR randomized controlled trial&lt;/a&gt; found that experienced developers using Cursor completed real-world tasks 19% slower with AI than without, while still believing the tools would save them around 24% of their time.&lt;/p&gt;

&lt;p&gt;The perception-reality gap is the most dangerous part. Developers, and by extension their managers, are systematically overestimating how much they're learning and growing.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem with "Volume" Metrics
&lt;/h2&gt;

&lt;p&gt;Here's where the measurement problem compounds everything.&lt;/p&gt;

&lt;p&gt;If you evaluate a junior engineer on lines of code, tickets closed, or PR velocity, you've just handed them the wrong objective function. &lt;a href="https://buttondown.com/hillelwayne/archive/goodharts-law-in-software-engineering/" rel="noopener noreferrer"&gt;Goodhart's Law&lt;/a&gt; states that when a measure becomes a target, it ceases to be a good measure. In software engineering, it's always been a problem. Under AI, it becomes catastrophic.&lt;/p&gt;

&lt;p&gt;A junior developer measured on ticket velocity has a clear path to success: accept the first thing Cursor outputs, open a PR, move to the next ticket. The metric goes up. The learning doesn't. They're not a developer anymore. They're a human proxy for an LLM, and &lt;a href="https://www.gitclear.com/ai_assistant_code_quality_2025_research" rel="noopener noreferrer"&gt;GitClear's analysis of 211 million changed lines of code&lt;/a&gt; shows exactly what this looks like in a codebase: refactoring activity dropped from 25% of changed lines in 2021 to under 10% by 2024. Copy-pasted code increased nearly 50%, from 8.3% to 12.3% of changed lines.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://martinfowler.com/bliki/CannotMeasureProductivity.html" rel="noopener noreferrer"&gt;Martin Fowler&lt;/a&gt; has argued for decades that "copy and paste programming leads to high lines of code counts and poor design." AI-assisted development has automated copy-paste at scale. The metrics look great. The codebase quietly degrades.&lt;/p&gt;

&lt;p&gt;And teams reinforce this. If your sprint retros celebrate ticket throughput and your 1:1s ask "what did you ship this week?", you're training your juniors to optimize for output over understanding. It's Goodhart's Law on steroids, because AI gives juniors the ability to hit the metric convincingly without doing the underlying work.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://newsletter.pragmaticengineer.com/p/measuring-developer-productivity" rel="noopener noreferrer"&gt;Kent Beck's model&lt;/a&gt; frames this as Effort, Output, Outcome, Impact. The mistake is measuring juniors on Output when what actually matters for their development is the quality of the Effort. Are they building understanding? Are they encountering and overcoming resistance? Or are they just turning prompts into PRs?&lt;/p&gt;

&lt;h2&gt;
  
  
  The Solution: Optimize for "Learning Velocity"
&lt;/h2&gt;

&lt;p&gt;We need to change the definition of a junior's job from "shipping code" to "acquiring context."&lt;/p&gt;

&lt;p&gt;That sounds idealistic until you think about what a junior is actually worth to your organization. The value is not their output today. It's their judgment in three years. Every task they genuinely understand, every bug they trace to its root cause, every architectural tradeoff they see play out in production, that's the compounding asset you're building. AI can inflate short-term output. It cannot shortcut the accumulation of engineering intuition.&lt;/p&gt;

&lt;p&gt;This is where &lt;a href="https://www.span.app/" rel="noopener noreferrer"&gt;Span&lt;/a&gt; offers a genuinely different lens. Rather than counting commits or tickets, Span tracks ramp-up patterns at the developer level, giving engineering managers visibility into whether a junior is actually growing in scope and independence, or whether they're stuck in a loop of simple tasks regardless of how many PRs they're opening.&lt;/p&gt;

&lt;p&gt;The key metric is Onboarding Velocity: tracking not just whether a junior merged their first PR, but how long it takes to reach their 10th, their 20th, and what the complexity profile of those PRs looks like over time. A junior who hits their 10th PR in week two by mass-prompting Cursor is not on the same trajectory as one who hits it in week six by working through progressively harder problems with genuine comprehension.&lt;/p&gt;

&lt;p&gt;The detail Span surfaces is the rate of growth: is a junior's scope expanding over time, or are they still doing the same class of task at month four that they were doing at month one? That distinction is invisible to any tool that only counts activity. It's the difference between a junior who is on track to become a strong mid-level engineer and one who will be a permanent prompt-relay.&lt;/p&gt;

&lt;h2&gt;
  
  
  Practical Mentorship for the AI Era
&lt;/h2&gt;

&lt;p&gt;Reframing the metric is necessary but not sufficient. You also have to change what you ask juniors to do, and how you structure your involvement.&lt;/p&gt;

&lt;p&gt;Three practices that actually work:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. The "No-AI" Sandbox.&lt;/strong&gt; Assign specific tasks where AI assistance is off-limits. These don't have to be large. In fact, they shouldn't be. The point is forcing a junior to build a mental model before reaching for a shortcut. Debug this failing test without any external help. Trace this API call from the controller to the database by reading the code. The discomfort is the mechanism. You're not punishing them; you're creating the conditions for the muscle memory that AI use requires but cannot build on its own.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Reverse Code Review.&lt;/strong&gt; Have your junior review the AI's output, or yours. Ask them to explain every line they didn't write. If they can't, that's diagnostic information, not a performance failure. It tells you where the gaps are and where to focus mentorship. The &lt;a href="https://engineeringenablement.substack.com/p/5-strategies-for-mentoring-junior" rel="noopener noreferrer"&gt;Engineering Enablement approach&lt;/a&gt; suggests requiring juniors to annotate AI-generated code in PRs with explicit notes on what they verified, what they changed, and why they trusted or rejected sections of the output. This turns passive AI use into an active comprehension exercise.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Measure "Review Depth."&lt;/strong&gt; Span's PR review tracking lets you see whether a junior is leaving substantive comments on other people's code or just approving ("LGTM"). A developer who asks meaningful questions in review, who catches edge cases, who pushes back on naming or architectural choices, is building the judgment that will compound. A developer who rubber-stamps everything is not. These signals are invisible without deliberate measurement.&lt;/p&gt;

&lt;p&gt;The broader reframe here is that mentorship in the AI era is less about showing juniors how to write code and more about teaching them how to interrogate it. The hard skills shift: reading and evaluating code becomes more important than writing it from scratch. System-level reasoning, the ability to ask "what happens to this service when that one fails?", becomes more valuable, not less, because AI tools are poor at it. &lt;a href="https://addyosmani.com/blog/next-two-years/" rel="noopener noreferrer"&gt;Addy Osmani&lt;/a&gt; describes senior engineers increasingly serving as "AI code reviewers," which means the skill of critical evaluation is now both the most important skill a junior can develop and the one most at risk of atrophy.&lt;/p&gt;

&lt;p&gt;This takes explicit investment. As Wanderson Lacerda &lt;a href="https://medium.com/@w.lacerda/the-hollowing-out-why-the-junior-developer-is-an-endangered-species-and-what-that-means-for-the-f72f9c8242e4" rel="noopener noreferrer"&gt;argues&lt;/a&gt;, training juniors is no longer a byproduct of getting work done. It's a capital expense. You have to budget for it, structure it, and measure it, or it won't happen.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;The short-term math on cutting junior hiring is correct. AI is cheaper than a new grad for boilerplate. But the five-year math runs the other way. The systems AI is generating today will need human engineers to maintain, extend, and fix. The &lt;a href="https://cloud.google.com/blog/products/devops-sre/announcing-the-2024-dora-report" rel="noopener noreferrer"&gt;Google DORA 2024 Report&lt;/a&gt; found that for every 25% increase in AI adoption, delivery stability decreased 7.2%. Somebody has to own that stability. That somebody needs deep engineering judgment. That judgment takes years to build, and you can't build it if you stop hiring the people who need to develop it.&lt;/p&gt;

&lt;p&gt;Mentorship is no longer a nice-to-have program on the culture page of your handbook. It's a survival strategy for the industry. Protect the junior role, not out of charity, but because your senior pipeline in 2030 depends entirely on the juniors you invest in today.&lt;/p&gt;

&lt;p&gt;The teams that figure this out, that measure learning velocity instead of ticket count, that create deliberate space for skill formation alongside AI use, are the ones who will have senior engineers with real judgment when the rest of the industry is trying to figure out why their AI-generated codebase is on fire and nobody knows why.&lt;/p&gt;

&lt;p&gt;Are you worried about the next generation of engineers? Share your best strategy for mentoring in the age of AI in the comments below.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>developer</category>
    </item>
    <item>
      <title>"LGTM": Are We Reviewing Code or Just Rubber Stamping It?</title>
      <dc:creator>Jakkie Koekemoer</dc:creator>
      <pubDate>Thu, 19 Feb 2026 10:41:51 +0000</pubDate>
      <link>https://forem.com/jakkie_koekemoer/lgtm-are-we-reviewing-code-or-just-rubber-stamping-it-ke4</link>
      <guid>https://forem.com/jakkie_koekemoer/lgtm-are-we-reviewing-code-or-just-rubber-stamping-it-ke4</guid>
      <description>&lt;p&gt;You open a 1,200-line PR at 9 AM. The CI is green. You scroll for four minutes, click "Approve," type "LGTM," and move to the next one.&lt;/p&gt;

&lt;p&gt;You didn't review it. You logged it.&lt;/p&gt;

&lt;p&gt;AI coding assistants generate modules in seconds. But reading code happens at human speed. This asymmetry creates a bottleneck: code influx permanently exceeds verification capacity. We're not reviewing anymore. We're performing security theater in the PR queue.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Tech Debt Singularity
&lt;/h2&gt;

&lt;p&gt;The Tech Debt Singularity is when incoming code rate exceeds review rate forever. Not a temporary backlog. A structural break.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Felpce71gugc5qyaqyu88.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Felpce71gugc5qyaqyu88.png" alt="The Tech Debt Singularity" width="506" height="710"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;LLMs don't get bored writing 50 similar functions. They don't care if the PR is 2,000 lines. Humans do. We skim. We trust the tests. We hope.&lt;/p&gt;

&lt;p&gt;The result: code that works in isolation but fights itself architecturally. Six months later, you add a feature and realize the codebase is incoherent. One module uses factories. Another uses dependency injection. A third hard-codes everything. Each decision was locally correct. Together, they're a maze.&lt;/p&gt;

&lt;p&gt;Research shows bugs caught in review &lt;a href="https://www.nist.gov/system/files/documents/director/planning/report02-3.pdf" rel="noopener noreferrer"&gt;cost exponentially less than bugs in production&lt;/a&gt;. But only if you actually review the code.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three Symptoms You're Rubber Stamping
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. The Nitpick Review
&lt;/h3&gt;

&lt;p&gt;600-line PR. Dense logic with state management, async flows, error handling. Understanding it takes 30 minutes. You have 10.&lt;/p&gt;

&lt;p&gt;You rename &lt;code&gt;userData&lt;/code&gt; to &lt;code&gt;userProfile&lt;/code&gt;. Point out a missing space. Flag a linting error CI already caught. Three thoughtful-looking comments. Click "Approve."&lt;/p&gt;

&lt;p&gt;You reviewed syntax. Not logic.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Time-to-Approve Collapse
&lt;/h3&gt;

&lt;p&gt;1,200 lines approved in four minutes. Even scanning at 300 lines per minute, you can't verify logic, edge cases, or abstraction quality. You can scroll. You can spot-check. You can't review.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://google.github.io/eng-practices/review/reviewer/speed.html" rel="noopener noreferrer"&gt;Google's research&lt;/a&gt; confirms: meaningful reviews take time. When approval time drops while PR size stays constant, you're not reviewing faster. You're reviewing less.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Blind Trust in AI
&lt;/h3&gt;

&lt;p&gt;"Generated by o1, so the logic is sound." Wrong.&lt;/p&gt;

&lt;p&gt;AI assistants generate plausible code. They don't understand your business logic. Your payment service has a race condition when webhooks arrive simultaneously. Your database uses soft-deletes that need special handling. The LLM doesn't know. It generates code that looks right but breaks in context.&lt;/p&gt;

&lt;p&gt;Research backs this up. &lt;a href="https://www.coderabbit.ai/blog/state-of-ai-vs-human-code-generation-report" rel="noopener noreferrer"&gt;CodeRabbit's analysis&lt;/a&gt; of 470 open-source PRs found AI-generated code contained 1.7x more issues overall than human-written code. Logic and correctness issues were 75% more common. Error handling gaps appeared at nearly 2x the rate. Security vulnerabilities, particularly XSS, showed up at 2.74x higher rates.&lt;/p&gt;

&lt;p&gt;The fundamental problem: &lt;a href="https://survey.stackoverflow.co/2025/ai#2-ai-tool-frustrations" rel="noopener noreferrer"&gt;66% of developers cite&lt;/a&gt; "AI solutions that are almost right, but not quite" as their biggest frustration with AI tools. Teams at &lt;a href="https://www.span.app" rel="noopener noreferrer"&gt;Span&lt;/a&gt; have been measuring this effect across millions of lines of code. Their data shows that AI-generated code creates specific patterns of defects that require different verification strategies than human-written code.&lt;/p&gt;

&lt;p&gt;Without careful review, you catch these issues in production.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Review Quality Framework
&lt;/h2&gt;

&lt;p&gt;You need metrics that show when you're cutting corners. Most engineering metrics tools track PR velocity or cycle time. Metrics that reward speed. But speed isn't safety.&lt;/p&gt;

&lt;p&gt;What matters is review quality. Three metrics expose rubber stamping:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvyu50uqkqo28iy87q9oi.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvyu50uqkqo28iy87q9oi.png" alt="The Review Quality Framework" width="800" height="318"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Review Burden Ratio
&lt;/h3&gt;

&lt;p&gt;Lines changed and complexity vs. time spent reviewing. When large, complex PRs get approved in minutes, your ratio collapses. This is your smoke detector.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Review Coverage
&lt;/h3&gt;

&lt;p&gt;Percentage of lines actually viewed, not just scrolled past. Under 80% coverage means skimming. GitHub and GitLab track this in their APIs, but most teams never look at it.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Time to Substantive Feedback
&lt;/h3&gt;

&lt;p&gt;How long before real feedback, not linting nitpicks. If first comments are always "rename this variable" or "fix spacing," you're reviewing cosmetics while skipping logic.&lt;/p&gt;

&lt;p&gt;When teams track these metrics, patterns emerge. One team found their velocity was up 40% but review depth down 60%. They were shipping bugs faster.&lt;/p&gt;

&lt;p&gt;The challenge: most teams don't track this systematically. It requires correlating PR metadata with actual reviewer activity, understanding code complexity, and distinguishing substantive review from rubber stamping. This is particularly critical now that AI-generated code is flooding PRs. Span's research shows this code requires different verification strategies, making review depth metrics even more important.&lt;/p&gt;

&lt;h2&gt;
  
  
  Five Strategies to Stop Rubber Stamping
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Enforce 400-Line PR Limits
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://smartbear.com/learn/code-review/best-practices-for-peer-code-review/" rel="noopener noreferrer"&gt;SmartBear's study with Cisco&lt;/a&gt; found review effectiveness drops sharply after 400 lines. Beyond that, reviewers skim.&lt;/p&gt;

&lt;p&gt;Set a hard limit: 400 lines maximum. Larger changes need multiple PRs with clear dependencies. This slows shipping. That's the point. You're choosing correctness over speed.&lt;/p&gt;

&lt;p&gt;AI makes this easier. Ask Claude or ChatGPT to split a 1,200-line refactor into three atomic PRs. It takes seconds.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Use AI-Generated PR Summaries
&lt;/h3&gt;

&lt;p&gt;Have AI compress the PR into a summary before review:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What changed and why&lt;/li&gt;
&lt;li&gt;High-level architecture decisions&lt;/li&gt;
&lt;li&gt;Edge cases considered&lt;/li&gt;
&lt;li&gt;Tests added&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The summary doesn't replace review. It gives you context so you can focus on logic instead of parsing syntax. Read the summary first, then review the code.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Surface Declining Review Quality to Management
&lt;/h3&gt;

&lt;p&gt;When Review Burden Ratio trends down, show management the data. "We can't review this volume safely. We need to slow shipping or add reviewers."&lt;/p&gt;

&lt;p&gt;This feels like admitting failure. But the alternative is shipping unreviewed code and catching bugs in production. Multiple studies show code review catches the majority of defects before release. Rubber stamping defers those bugs.&lt;/p&gt;

&lt;p&gt;Treat reviewer capacity as a hard constraint. If you won't deploy without tests, don't deploy without real reviews.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Rotate Review Responsibilities
&lt;/h3&gt;

&lt;p&gt;Don't bottleneck all reviews through one senior engineer. Rotate across the team. This distributes effort and reduces the temptation to rubber stamp when overwhelmed.&lt;/p&gt;

&lt;p&gt;Junior engineers improve at reviewing through practice. Senior engineers get breathing room.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Make "I Don't Have Time" Acceptable
&lt;/h3&gt;

&lt;p&gt;Create a culture where "I can't review this properly right now" is acceptable. This beats rushed approval.&lt;/p&gt;

&lt;p&gt;If reviewers consistently lack time, that's data. The team is under-resourced for the code volume being generated. Use that signal to adjust expectations or add capacity.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Decision Framework: When Is a Review Good Enough?
&lt;/h2&gt;

&lt;p&gt;Use this three-question framework before approving:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Can I explain the core logic to someone else?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If you can't summarize what the code does and why, you didn't review it. You scanned it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Have I considered failure modes?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;What breaks if the network times out? What happens with malformed input? If you haven't thought about edge cases, you didn't review.&lt;/p&gt;

&lt;p&gt;This is particularly important for AI-generated code. Span's analysis shows AI code contains significantly more error handling gaps and edge case issues than human code. You need to actively verify these patterns, not assume they're handled.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Does this fit our architecture?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Does this match existing patterns? If it introduces a new approach, is that justified? Inconsistent patterns compound into technical debt.&lt;/p&gt;

&lt;p&gt;If you answer "no" to any question, don't approve. Request time or ask for the PR to be split.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Rubber Stamping Actually Costs
&lt;/h2&gt;

&lt;p&gt;Rubber stamping erodes architecture gradually. When nobody reads code holistically, each PR optimizes locally. Together, they create global incoherence.&lt;/p&gt;

&lt;p&gt;Example: You approve three PRs in one day. One uses factories. Another uses dependency injection. A third hard-codes dependencies. None are wrong individually. Together, they create inconsistency.&lt;/p&gt;

&lt;p&gt;Six months later, new engineers spend weeks learning idiosyncrasies. Features take longer because every change navigates conflicting patterns. This is how brownfield codebases form. Not from malice. From inattention compounded over time.&lt;/p&gt;

&lt;p&gt;AI accelerates this. It generates more code faster than humans can, magnifying the cost of each unreviewed decision. And the data shows AI code creates specific architectural challenges. &lt;a href="https://www.gitclear.com/coding_on_copilot_data_shows_ais_downward_pressure_on_code_quality" rel="noopener noreferrer"&gt;GitClear's analysis&lt;/a&gt; of 153 million changed lines of code found code churn doubled and copy/pasted code increased significantly, with AI-generated code resembling "an itinerant contributor, prone to violate the DRY-ness of the repos visited."&lt;/p&gt;

&lt;p&gt;The teams who understand this best are those measuring it. Span's platform detects AI-generated code at the chunk level, letting teams see exactly where these patterns appear and correlate them with code quality outcomes. This visibility matters because you can't fix what you can't measure.&lt;/p&gt;

&lt;h2&gt;
  
  
  Implementation Checklist
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;This week:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Set a 400-line PR size limit in your repo settings&lt;/li&gt;
&lt;li&gt;Add a PR template that requires a summary section&lt;/li&gt;
&lt;li&gt;Start tracking Review Burden Ratio manually (spreadsheet with PR size, review time, approval)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;This month:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Review your team's average time-to-approve vs. PR size&lt;/li&gt;
&lt;li&gt;Identify PRs approved in under 5 minutes that exceeded 500 lines&lt;/li&gt;
&lt;li&gt;Present findings to your team&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;This quarter:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Implement automated review quality tracking&lt;/li&gt;
&lt;li&gt;Rotate review responsibilities weekly&lt;/li&gt;
&lt;li&gt;Measure defect rates before and after implementing limits&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Code review is the last defense against chaos. When you rubber stamp, you're crossing your fingers and hoping tests are comprehensive.&lt;/p&gt;

&lt;p&gt;The temptation to skim is constant. You're busy. The PR looks fine. CI is green. But "looks fine" isn't "is fine." This is especially true now that AI code makes up a growing percentage of PRs. Code that looks correct but contains subtle defects requires thorough verification.&lt;/p&gt;

&lt;p&gt;Set limits. Track data. When Review Burden Ratio drops, treat it as a signal to slow down or add capacity.&lt;/p&gt;

&lt;p&gt;AI code generation is fast. But speed without verification is just technical debt accumulating faster than you can pay it off. The hard part isn't generating code. The hard part is understanding it.&lt;/p&gt;

&lt;p&gt;That part is still on you.&lt;/p&gt;

&lt;p&gt;Be honest: Have you approved a PR this week that you didn't actually read? Tell us your "Rubber Stamp" horror stories in the comments.&lt;/p&gt;

</description>
      <category>coding</category>
      <category>programming</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Copilot vs. Cursor vs. Reality: Why 59% of Developers Use Both (And Then Some)</title>
      <dc:creator>Jakkie Koekemoer</dc:creator>
      <pubDate>Fri, 13 Feb 2026 12:48:00 +0000</pubDate>
      <link>https://forem.com/jakkie_koekemoer/copilot-vs-cursor-vs-reality-why-59-of-developers-use-both-and-then-some-32np</link>
      <guid>https://forem.com/jakkie_koekemoer/copilot-vs-cursor-vs-reality-why-59-of-developers-use-both-and-then-some-32np</guid>
      <description>&lt;p&gt;Here's the open secret on your engineering floor: your company pays for GitHub Copilot Enterprise, but you've been sneaking Cursor sessions since December. Your tech lead pretends not to notice because she's running Claude Code in her terminal for the gnarly refactors. The intern, bless him, still thinks we all follow the "approved tooling" memo.&lt;/p&gt;

&lt;p&gt;We don't. According to Microsoft's own research, &lt;a href="https://www.microsoft.com/en-us/worklab/work-trend-index/ai-at-work-is-here-now-comes-the-hard-part" rel="noopener noreferrer"&gt;78% of AI users now bring their own tools to work&lt;/a&gt;, and a Gartner survey found &lt;a href="https://www.gartner.com/en/newsroom/press-releases/2025-11-19-gartner-identifies-critical-genai-blind-spots-that-cios-must-urgently-address0" rel="noopener noreferrer"&gt;69% of organizations&lt;/a&gt; either know or suspect their developers are using prohibited AI tools. This isn't shadow IT in the malicious sense. It's an efficiency signal. Developers aren't sneaking tools to cause problems. They're sneaking tools because the sanctioned ones can't keep up.&lt;/p&gt;

&lt;p&gt;The "One Tool to Rule Them All" procurement strategy made sense in 2022. In 2026, it's actively killing velocity.&lt;/p&gt;

&lt;h2&gt;
  
  
  We fragmented because one tool can't serve three cognitive modes
&lt;/h2&gt;

&lt;p&gt;The AI coding landscape splintered for a reason. The tools that won aren't competing for the same job. They're serving fundamentally different cognitive functions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The "Safe" choice is Copilot.&lt;/strong&gt; GitHub reports &lt;a href="https://techcrunch.com/2025/07/30/github-copilot-crosses-20-million-all-time-users/" rel="noopener noreferrer"&gt;90% of Fortune 100 companies&lt;/a&gt; now use it, and its enterprise features, SSO, audit logs, compliance certifications, make procurement teams happy. It generates &lt;a href="https://github.blog/news-insights/research/research-quantifying-github-copilots-impact-on-developer-productivity-and-happiness/" rel="noopener noreferrer"&gt;46% of all code&lt;/a&gt; written by its users, up from 27% at launch. For inline autocomplete in familiar patterns, it works fine. But for power users, Copilot increasingly feels like a reliable sedan when you need a rally car. The \$39/seat/month Enterprise tier buys you safety features, not horsepower.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The "Flow" choice is Cursor.&lt;/strong&gt; Cursor crossed &lt;a href="https://research.contrary.com/company/anysphere" rel="noopener noreferrer"&gt;1 million daily active users&lt;/a&gt; by December 2025 and hit a \$29 billion valuation by November. It's a VS Code fork rebuilt around AI-first workflows, not an extension bolted on afterward. The difference matters. Cursor owns your entire context window. Its Composer mode can coordinate multi-file edits from a single prompt. Its Tab completion has sub-400ms latency because they acquired Supermaven specifically for speed. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The "Agent" choice is Claude Code.&lt;/strong&gt; Anthropic's terminal-native tool hit &lt;a href="https://www.anthropic.com/news/anthropic-acquires-bun-as-claude-code-reaches-usd1b-milestone" rel="noopener noreferrer"&gt;\$1 billion ARR&lt;/a&gt; in just six months, making it one of the fastest-growing enterprise products ever. Claude Code doesn't live in your IDE. It lives in your terminal, executes commands, creates pull requests, and handles 90%+ of git interactions for developers who use it heavily. For complex refactors, multi-step autonomous tasks, and codebase exploration, it's a different beast entirely.&lt;/p&gt;

&lt;p&gt;Here's what senior developers figured out: these tools serve three distinct cognitive modes. &lt;strong&gt;Flow&lt;/strong&gt; is the tab-tab autocomplete while you're typing, keeping you in the zone. &lt;strong&gt;Reasoning&lt;/strong&gt; is the chat interface when you're stuck, rubber-ducking a problem. &lt;strong&gt;Autonomy&lt;/strong&gt; is offloading entire tasks to an agent while you work on something else. Copilot dominates the middle. Cursor owns Flow. Claude Code wins Autonomy. Asking which is "best" misses the point. They're not interchangeable.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjw6l920hff6vehfjy73v.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjw6l920hff6vehfjy73v.png" alt="The Three Developer Cognitive Modes in AI-Assisted Coding" width="736" height="454"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://www.infoworld.com/article/4077352/85-of-developers-use-ai-regularly-jetbrains-survey.html" rel="noopener noreferrer"&gt;JetBrains 2025 survey&lt;/a&gt; confirms this fragmentation: GitHub Copilot sits at 30% usage and ChatGPT at 41% among developers who use AI tools regularly. According to Second Talent, &lt;a href="https://www.secondtalent.com/resources/ai-coding-assistant-statistics/" rel="noopener noreferrer"&gt;59% of developers now run three or more AI coding tools in parallel&lt;/a&gt;. This isn't chaos, it's optimization. Developers pick tools based on task requirements. Even internally at &lt;a href="https://draft.dev" rel="noopener noreferrer"&gt;Draft.dev&lt;/a&gt;, we use different models based on their strengths. Smart teams use the right tool for the job.&lt;/p&gt;

&lt;h2&gt;
  
  
  The friction of "approved tooling" burns money and momentum
&lt;/h2&gt;

&lt;p&gt;When your approved IDE and your preferred AI tool don't match, you're switching contexts constantly. The mental overhead compounds throughout the day.&lt;/p&gt;

&lt;p&gt;But here's the number that should scare procurement: &lt;strong&gt;53% of enterprise software licenses go unused&lt;/strong&gt;, according to &lt;a href="https://zylo.com/research/saas-management-index/" rel="noopener noreferrer"&gt;Zylo's 2025 SaaS Management Index&lt;/a&gt;. Gartner calls this "shelfware" and estimates it represents &lt;strong&gt;30% of typical SaaS spend&lt;/strong&gt;. For the average enterprise, that's &lt;a href="https://zylo.com/blog/manage-saas-licenses-with-usage-insights/" rel="noopener noreferrer"&gt;\$21 million annually&lt;/a&gt; in wasted licenses.&lt;/p&gt;

&lt;p&gt;The pattern is everywhere. A 200-engineer company paying \$39/seat/month for Copilot Enterprise spends roughly \$94,000 a year. Usage dashboards often show maybe 40% of seats active. Half of those active users have Cursor Pro on their personal credit cards anyway. They're not getting Copilot Enterprise value. They're funding zombie licenses while developers self-fund the tools they actually need.&lt;/p&gt;

&lt;p&gt;IT procures the "safe" choice to satisfy security and compliance. Developers trial it, find it doesn't match how they actually work, then quietly install what does. According to Zylo's 2026 SaaS Management Index, AI-native app adoption surged &lt;a href="https://zylo.com/reports/2026-saas-management-index/" rel="noopener noreferrer"&gt;108% year-over-year overall&lt;/a&gt; (393% for large enterprises), largely driven by expense-based purchasing outside formal procurement. The approved stack isn't designed around developer experience, it's designed around vendor relationships and audit checkboxes.&lt;/p&gt;

&lt;h2&gt;
  
  
  The data gap that makes these conversations impossible
&lt;/h2&gt;

&lt;p&gt;Here's the core problem: every conversation about switching tools hits the same wall. "That's just your preference. We can't justify budget based on vibes."&lt;/p&gt;

&lt;p&gt;This objection is completely reasonable. You can't walk into a budget meeting and say "Cursor feels faster." That doesn't survive a procurement committee. You need numbers. But AI tool telemetry is a mess. Each vendor reports different metrics, measured differently, with obvious incentives to look good. GitHub tells you Copilot acceptance rates. Cursor tells you completions. Neither tells you what actually shipped or whether the code stuck.&lt;/p&gt;

&lt;p&gt;The missing piece is independent measurement. You need to see what's actually happening in your codebase, not what vendors claim in their dashboards. This is where platforms like &lt;a href="https://span.app/" rel="noopener noreferrer"&gt;Span&lt;/a&gt; come in.&lt;/p&gt;

&lt;p&gt;Span is a developer intelligence platform, but the feature that changes the conversation is their AI Code Detector. It doesn't rely on vendor telemetry or IDE integrations. Instead, it uses a model called span-detect-1 to analyze code artifacts directly, classifying each chunk as AI-generated or human-written with &lt;a href="https://www.span.app/blog/introducing-span-detect-1" rel="noopener noreferrer"&gt;95% accuracy&lt;/a&gt; across Python, TypeScript, JavaScript, Ruby, and other languages.&lt;/p&gt;

&lt;p&gt;Why does this matter? Because it works universally across all AI coding tools. Copilot, Cursor, Claude, ChatGPT copy-paste, it doesn't matter. Span sees what actually landed in your codebase. No self-reporting. No vendor dashboards. Just ground truth from code.&lt;/p&gt;

&lt;p&gt;This kind of data changes the argument entirely. Instead of "I prefer Cursor," you can say: "Developers using Cursor have a 23% higher AI code ratio on feature branches and ship with 18% less rework in code review." That's a conversation procurement can act on. You're not asking them to trust anyone's intuition. You're showing them which tools actually correlate with output and quality.&lt;/p&gt;

&lt;p&gt;Span also tracks investment mix, DORA metrics, and can correlate AI code ratios with defect rates over time. For leaders trying to justify AI budgets to executives, that's the missing link. Most companies can only measure utilization because that's what vendor dashboards show. Span lets you measure impact at the code level.&lt;/p&gt;

&lt;h2&gt;
  
  
  A proposal for 2026: the "BYOAI" stipend
&lt;/h2&gt;

&lt;p&gt;Here's the pitch to engineering leadership everywhere: stop procuring AI coding tools like it's 2015.&lt;/p&gt;

&lt;p&gt;We already treat hardware this way. Most companies gave up mandating specific laptops years ago. You get a budget, you pick your machine, IT ensures it meets security requirements. The same model works for AI tools.&lt;/p&gt;

&lt;p&gt;Give developers a &lt;strong&gt;monthly AI stipend&lt;/strong&gt;, maybe \$50-75, and let them pick their stack. &lt;a href="https://www.cursor.com/pricing" rel="noopener noreferrer"&gt;Cursor Pro is \$20/month&lt;/a&gt;. &lt;a href="https://claude.com/pricing" rel="noopener noreferrer"&gt;Claude Pro is \$20/month&lt;/a&gt;. &lt;a href="https://github.com/features/copilot/plans" rel="noopener noreferrer"&gt;Copilot Pro is \$10/month&lt;/a&gt;. A developer could run all three for less than what you're paying for unused Enterprise seats. Some will stick with Copilot. Some will go all-in on Cursor. Some will mix depending on the task. That's the point.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnzlnz4k9vtw4spmwzoj8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnzlnz4k9vtw4spmwzoj8.png" alt="Traditional Procurement Model" width="800" height="193"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyb0w3mk8wh8mn1qn0vge.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyb0w3mk8wh8mn1qn0vge.png" alt="BYOAI Stipend Model" width="800" height="96"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;"But what about security and compliance?" Fair question. The answer isn't locking down inputs. It's observing outputs. Tools like Span give you visibility into what's actually happening in your codebase without mandating specific tools. You can enforce policies at the code level, flagging AI-generated code for additional review, tracking quality metrics by tool, identifying which patterns correlate with defects, rather than trying to control which autocomplete a developer uses.&lt;/p&gt;

&lt;p&gt;This flips the mental model. Instead of "approve tools, hope they get used," you "observe outcomes, fund what works." The developers who generate the highest-quality AI-assisted code with the fastest cycle times are probably using the tools that work best for their style. Learn from them instead of fighting them.&lt;/p&gt;

&lt;p&gt;IBM's &lt;a href="https://www.ibm.com/think/x-force/2025-cost-of-a-data-breach-navigating-ai" rel="noopener noreferrer"&gt;Cost of a Data Breach report&lt;/a&gt; found that organizations with high shadow AI had \$670,000 higher breach costs, primarily because of missing access controls and visibility. The solution isn't banning shadow AI, that's been failing for three years. The solution is bringing it into the light with proper observability while respecting developer autonomy.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fz03xfvwuluvtbsuotu0z.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fz03xfvwuluvtbsuotu0z.png" alt="The Hidden Cost of Shadow AI Breaches" width="689" height="222"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The best tool is the one that gets used
&lt;/h2&gt;

&lt;p&gt;Here's the uncomfortable truth: &lt;a href="https://survey.stackoverflow.co/2025/" rel="noopener noreferrer"&gt;84% of developers&lt;/a&gt; now use AI coding tools, but trust in AI output has &lt;a href="https://survey.stackoverflow.co/2025/" rel="noopener noreferrer"&gt;dropped to 33%&lt;/a&gt;. Adoption is universal. Enthusiasm is not. Developers are pragmatists. They'll use what helps and abandon what doesn't, regardless of what's on the approved list.&lt;/p&gt;

&lt;p&gt;If you're a manager reading this, stop fighting the shadow stack. The war is over, and the shadow stack won. Your job now is to measure it, fund it, and get out of the way. The companies that will win the next few years of engineering productivity aren't the ones with the tightest tool controls. They're the ones that figured out how to harness developer agency while maintaining visibility into what ships.&lt;/p&gt;

&lt;p&gt;The "One Tool to Rule Them All" era is over. Welcome to the multi-tool reality. Your developers already live here.&lt;/p&gt;




&lt;p&gt;We all have that one "unapproved" AI tool we can't live without. I'm curious, what's in your shadow stack right now? Let's argue about the best setup in the comments.&lt;/p&gt;

</description>
      <category>githubcopilot</category>
      <category>ai</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Why I Ignore DORA Metrics (And What I Track Instead)</title>
      <dc:creator>Jakkie Koekemoer</dc:creator>
      <pubDate>Tue, 03 Feb 2026 09:52:00 +0000</pubDate>
      <link>https://forem.com/jakkie_koekemoer/why-i-ignore-dora-metrics-and-what-i-track-instead-cim</link>
      <guid>https://forem.com/jakkie_koekemoer/why-i-ignore-dora-metrics-and-what-i-track-instead-cim</guid>
      <description>&lt;p&gt;Your deployment frequency is up 40%. Lead time for changes is down. The DORA dashboard is glowing green. Your manager is happy.&lt;/p&gt;

&lt;p&gt;Then your best engineer quits. She tells you in the exit interview: "I spent more time in meetings than writing code."&lt;/p&gt;

&lt;p&gt;You check the metrics again. They still look great. What did you miss?&lt;/p&gt;

&lt;p&gt;DORA metrics measure velocity. They don't measure whether your developers have time to think. Your team can hit elite DORA scores while engineers burn out from constant context switching. The deployments keep flowing, right up until your senior people start leaving.&lt;/p&gt;

&lt;p&gt;The dashboard says you're fast. The reality is you're fragmented.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Science of Flow vs Velocity
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://www.fastcompany.com/944128/worker-interrupted-cost-task-switching" rel="noopener noreferrer"&gt;Research from UC Irvine&lt;/a&gt; found that after an interruption, it takes an average of 23 minutes and 15 seconds to return to your original task. That's not a guess. That's controlled measurement of knowledge workers under observation.&lt;/p&gt;

&lt;p&gt;Here's what that means: if you get interrupted twice during what you thought was an hour of coding time, you're getting about 14 minutes of real work done.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fk8l6lqzxr93qaecvfg0y.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fk8l6lqzxr93qaecvfg0y.png" alt="Interruptions" width="800" height="436"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Flow state changes this equation entirely. When Mihaly Csikszentmihalyi spent 40 years studying optimal performance, he identified that flow requires uninterrupted concentration where challenge matches skill. You can't get there in 30-minute blocks between meetings.&lt;/p&gt;

&lt;p&gt;DORA metrics can't see this difference. A deployment is a deployment, whether it took 30 minutes of focused work or 6 hours of context-switching hell.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Real Problem: Fragmented Time
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://www.getclockwise.com/eng-meeting-benchmarks" rel="noopener noreferrer"&gt;Clockwise analyzed 1.5 million meetings&lt;/a&gt; across 80,000 engineers. The average software engineer spends 10.9 hours per week in meetings. That sounds manageable until you see the next number: 6.3 hours per week in fragmented time.&lt;/p&gt;

&lt;p&gt;Fragmented time is the 30-minute gap between standup and your 1:1. It's the 45 minutes between the architecture review and the sprint planning session. It's calendar space that looks empty but is functionally useless.&lt;/p&gt;

&lt;p&gt;You can't write meaningful code in 30 minutes. You definitely can't debug a complex system issue. At best, you answer Slack messages and feel busy.&lt;/p&gt;

&lt;p&gt;Here's what a fragmented calendar looks like:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fr8v823vqv0gihu9c4352.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fr8v823vqv0gihu9c4352.png" alt="Fragmented calendar" width="800" height="150"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This engineer had an 8-hour workday with only two meaningful blocks for deep work: 45 minutes before lunch and 90 minutes in the late afternoon. By the time you load your mental model of the codebase, half the block is gone.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.blog/news-insights/research/octoverse-spotlight-good-day-project/" rel="noopener noreferrer"&gt;GitHub's Good Day Project&lt;/a&gt; found that developers with minimal interruptions have an 82% chance of having a productive day. When interrupted most of the day, that drops to 7%.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpg96cz3y6xa5cp03jzle.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpg96cz3y6xa5cp03jzle.png" alt="Impact of interruptions" width="415" height="350"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Going from two to three meetings per day lowers your progress chances from 74% to 14%.&lt;/p&gt;

&lt;p&gt;Yet DORA metrics would show this engineer as equally productive on both types of days, as long as they shipped commits.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to Measure Flow
&lt;/h2&gt;

&lt;p&gt;Stop trusting your gut. Your calendar data tells the real story.&lt;/p&gt;

&lt;p&gt;Traditional engineering metrics only look at Git activity. They count commits, PRs, and deployments. They miss the calendar entirely.&lt;/p&gt;

&lt;p&gt;I've been working with developer intelligence platforms like &lt;a href="https://span.app" rel="noopener noreferrer"&gt;Span&lt;/a&gt; through the agency I'm working for, and I've seen how useful it is when you connect calendar data to code output. It shows you something most engineers have never seen: how much uninterrupted time you have.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8k1jxz0n0mwoej9chls8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8k1jxz0n0mwoej9chls8.png" alt="Meeting time breakdown" width="800" height="419"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This engineer thought they had 40 hours of work time. The data shows 20.5 hours of actual focus time, with 11.6 hours consumed by recurring meetings. The breakdown reveals where the time goes: 5.5 hours in 1:1s, 4.3 hours in small group meetings, and fragments in larger meetings.&lt;/p&gt;

&lt;p&gt;The maps directly to what Paul Graham calls &lt;a href="https://paulgraham.com/makersschedule.html" rel="noopener noreferrer"&gt;"Maker Hours" versus "Manager Hours"&lt;/a&gt;. Makers need blocks of at least 2-4 hours. Managers can work in 30-minute increments. When you see your calendar data broken down this way, you can spot the mismatch: you're being paid to make things, but your schedule looks like a manager's.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxriru6xcfsaduj8qlrrd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxriru6xcfsaduj8qlrrd.png" alt="Different schedules" width="800" height="181"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here's what the data typically reveals:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You thought you had 30 hours of coding time per week&lt;/li&gt;
&lt;li&gt;Your calendar shows only 19.6 hours of focus time (2+ hour blocks)&lt;/li&gt;
&lt;li&gt;After accounting for context switching, you get 12-14 hours of actual deep work&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The insight isn't that you're slow. The insight is that the system is slow.&lt;/p&gt;

&lt;p&gt;The fragmented time report becomes evidence. You're not making excuses. You're showing math.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to Reclaim Your Calendar
&lt;/h2&gt;

&lt;p&gt;Once you see the fragmentation, you can fix it. Here are three tactics that work:&lt;/p&gt;

&lt;h3&gt;
  
  
  Meeting Bankruptcy
&lt;/h3&gt;

&lt;p&gt;Decline every recurring meeting that lacks a clear agenda. If the organizer can't articulate what decisions will be made or what information will be shared, it's a status update disguised as a meeting.&lt;/p&gt;

&lt;p&gt;Status updates belong in async channels, not on your calendar.&lt;/p&gt;

&lt;h3&gt;
  
  
  Clustering
&lt;/h3&gt;

&lt;p&gt;Move all meetings to specific days or specific time blocks. Some teams do "Meeting Mondays and Thursdays." Others block 9-12 for meetings and protect afternoons.&lt;/p&gt;

&lt;p&gt;The goal is to create contiguous maker time, not scattered 45-minute gaps.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fi6ohcwa2ilmeutblakvc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fi6ohcwa2ilmeutblakvc.png" alt="Calendar tactics" width="744" height="302"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Async Updates
&lt;/h3&gt;

&lt;p&gt;Replace standup with a Slack thread. Replace status meetings with weekly written summaries. Replace "quick syncs" with Loom videos.&lt;/p&gt;

&lt;p&gt;Async communication is searchable, referenceable, and doesn't fracture your calendar. Real-time meetings should be reserved for decisions, not information transfer.&lt;/p&gt;

&lt;p&gt;Some developer intelligence platforms can automate this. They pull work from GitHub, Jira, and Slack to create weekly summaries without requiring humans to manually write status updates. You get the information transfer without the meeting.&lt;/p&gt;

&lt;p&gt;The conversation with your manager looks like this:&lt;/p&gt;

&lt;p&gt;"I know my deployment count looks lower than last quarter. Here's my fragmented time report. I had 4.2 hours of uninterrupted time per week last month. I'm not asking for special treatment. I'm asking for enough time to think."&lt;/p&gt;

&lt;p&gt;Show the data. Propose the clustering solution. Most managers will say yes because they're also drowning in meetings.&lt;/p&gt;

&lt;h2&gt;
  
  
  Better Flow Improves DORA
&lt;/h2&gt;

&lt;p&gt;Here's the irony: when you ignore DORA and optimize for flow, your DORA metrics usually improve.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://link.springer.com/article/10.1007/s11219-010-9104-9" rel="noopener noreferrer"&gt;A study of 10,000 programming sessions&lt;/a&gt; across 86 programmers found that only 10% of sessions resume coding in less than 1 minute after an interruption. The rest take significantly longer to rebuild context.&lt;/p&gt;

&lt;p&gt;More fragmentation means more interruptions. More interruptions means more bugs. More bugs means lower change failure rate.&lt;/p&gt;

&lt;p&gt;Better focus doesn't just make you feel better. It makes you faster and more accurate.&lt;/p&gt;

&lt;p&gt;The Shopify example is instructive: after &lt;a href="https://www.ciodive.com/news/Shopify-meeting-ban-productivity-remote-work/645969/" rel="noopener noreferrer"&gt;canceling recurring meetings with 3+ people&lt;/a&gt;, meeting time dropped 33% in two months. They expected to complete 25% more projects than the previous year.&lt;/p&gt;

&lt;p&gt;That's better DORA performance from fewer meetings, not more sprints.&lt;/p&gt;

&lt;p&gt;When you protect maker time, you write better code. Better code deploys faster and breaks less often. DORA metrics improve as a side effect of good engineering practices, not as a goal in themselves.&lt;/p&gt;

&lt;h2&gt;
  
  
  Your Dashboard Doesn't Define Your Worth
&lt;/h2&gt;

&lt;p&gt;DORA metrics were designed to measure organizational performance, not individual productivity. The DORA team explicitly warns against using their metrics for team-by-team comparison or individual assessment.&lt;/p&gt;

&lt;p&gt;Nicole Forsgren, who co-created DORA, went on to co-create the SPACE framework specifically because DORA was insufficient. The first letter in SPACE is S: Satisfaction and well-being. That was missing from DORA because DORA was never meant to capture the full developer experience.&lt;/p&gt;

&lt;p&gt;Your deployment frequency says nothing about whether you had to sacrifice your weekend to hit that number. Your lead time doesn't show that you haven't shipped anything meaningful in months because you're constantly fixing the fragile system held together with technical debt.&lt;/p&gt;

&lt;p&gt;Velocity is a management metric. Flow is a maker metric.&lt;/p&gt;

&lt;p&gt;Your most valuable resource isn't your time. It's your attention span. Every meeting fragments it. Every interruption depletes it. Every context switch costs you 23 minutes of refocus time.&lt;/p&gt;

&lt;p&gt;Protect your attention like it's your most valuable asset, because it is.&lt;/p&gt;

&lt;p&gt;The green dashboard can wait. Your ability to think can't.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;How do you protect your focus time?&lt;/strong&gt; I'd love to hear what's worked (or hasn't) for dealing with meeting overload. Drop a comment below.&lt;/p&gt;

</description>
      <category>productivity</category>
      <category>devops</category>
    </item>
  </channel>
</rss>
