<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Vou Embarcar</title>
    <description>The latest articles on DEV Community by Vou Embarcar (@vou_embarcar_602e285676c8).</description>
    <link>https://dev.to/vou_embarcar_602e285676c8</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4136324%2F8b21f0fd-3d66-4e6f-8d3d-e7df8317cc2b.png</url>
      <title>DEV Community: Vou Embarcar</title>
      <link>https://dev.to/vou_embarcar_602e285676c8</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/vou_embarcar_602e285676c8"/>
    <language>en</language>
    <item>
      <title>Public API for Brazilian Lottery Data: OpenAPI Spec and Use Cases</title>
      <dc:creator>Vou Embarcar</dc:creator>
      <pubDate>Mon, 21 Sep 2026 19:45:51 +0000</pubDate>
      <link>https://dev.to/vou_embarcar_602e285676c8/public-api-for-brazilian-lottery-data-openapi-spec-and-use-cases-2og6</link>
      <guid>https://dev.to/vou_embarcar_602e285676c8/public-api-for-brazilian-lottery-data-openapi-spec-and-use-cases-2og6</guid>
      <description>&lt;p&gt;Brazilian lottery data — federal draws, state lotteries, and the culturally embedded &lt;em&gt;jogo do bicho&lt;/em&gt; results — has historically been fragmented across dozens of small, unreliable sources. If you were a developer building a Brazilian-market app that needed lottery results as a data feed, your options were:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Scrape a portal (fragile, breaks constantly)&lt;/li&gt;
&lt;li&gt;Pay a proprietary vendor (BRL 500-2000/month, minimum-commit contracts)&lt;/li&gt;
&lt;li&gt;Build your own scraper stack&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;None of these are great. Recently I open-sourced the OpenAPI specification and made available a &lt;strong&gt;public, read-only, rate-limited HTTP API&lt;/strong&gt; for Brazilian lottery data at &lt;a href="https://deunobicho.online/api-publica" rel="noopener noreferrer"&gt;deunobicho.online/api-publica&lt;/a&gt;. This post documents the spec, the design decisions, and concrete integration examples.&lt;/p&gt;




&lt;h2&gt;
  
  
  What the API covers
&lt;/h2&gt;

&lt;p&gt;Six data domains:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Endpoint&lt;/th&gt;
&lt;th&gt;What it returns&lt;/th&gt;
&lt;th&gt;Update frequency&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/api/loterias/federal&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Federal lottery results&lt;/td&gt;
&lt;td&gt;2×/week (Wed &amp;amp; Sat)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/api/loterias/mega-sena&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Mega-Sena results&lt;/td&gt;
&lt;td&gt;2-3×/week&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/api/loterias/lotofacil&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Lotofácil results&lt;/td&gt;
&lt;td&gt;6×/week&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/api/loterias/quina&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Quina results&lt;/td&gt;
&lt;td&gt;6×/week&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/api/loterias/lotep&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;LOTEP (Paraíba state)&lt;/td&gt;
&lt;td&gt;Daily&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/api/bicho/{banca}/{turno}&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Jogo do bicho (informal)&lt;/td&gt;
&lt;td&gt;5-7×/day&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Plus supporting endpoints:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;/api/livro-dos-sonhos&lt;/code&gt; — 231 dream symbols mapped to groups&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/api/prova/{id}&lt;/code&gt; — SHA256 evidence for any published result&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/api/grupos&lt;/code&gt; — the 25 canonical &lt;em&gt;jogo do bicho&lt;/em&gt; animal groups&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Total surface: about &lt;strong&gt;25 routes&lt;/strong&gt;, all read-only, all cached at the CDN.&lt;/p&gt;




&lt;h2&gt;
  
  
  Design decisions
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Read-only, no auth for the free tier
&lt;/h3&gt;

&lt;p&gt;The vast majority of use cases (personal projects, cultural datasets, aggregation apps) do not need write access or personalization. Removing auth removes friction. Rate limiting is by IP (300 req/min) — enough for any legitimate use, insufficient for scraping-as-service abuse.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. JSON only, no XML, no SOAP
&lt;/h3&gt;

&lt;p&gt;We are in 2026. Every consumer speaks JSON. Every language has a &lt;code&gt;fetch&lt;/code&gt; / &lt;code&gt;curl&lt;/code&gt; / &lt;code&gt;requests&lt;/code&gt; primitive. Multiplying formats multiplies bugs.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. &lt;code&gt;force-static&lt;/code&gt; for archive endpoints
&lt;/h3&gt;

&lt;p&gt;Historical results never change. The archive endpoints are literally pre-built JSON files served from Vercel's edge:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;GET /api/loterias/federal/2026-09-18
Cache-Control: public, max-age=31536000, immutable
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;TTFB from anywhere in the world: &lt;strong&gt;32-80ms&lt;/strong&gt;. Zero server functions on the hot path.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. &lt;code&gt;Last-Modified&lt;/code&gt; and &lt;code&gt;ETag&lt;/code&gt; on hot endpoints
&lt;/h3&gt;

&lt;p&gt;The "today's results" endpoint returns proper HTTP caching headers:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;GET /api/loterias/federal/hoje
Last-Modified: Wed, 18 Sep 2026 19:15:00 GMT
ETag: "8f4a2b1c"
Cache-Control: public, max-age=60, s-maxage=60
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If a consumer polls, they can send &lt;code&gt;If-None-Match: "8f4a2b1c"&lt;/code&gt; and get a 304 Not Modified — zero bandwidth cost.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Consistent envelope
&lt;/h3&gt;

&lt;p&gt;Every response uses:&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;"meta"&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;"version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"generated_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;"2026-09-18T19:15: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;"cache_ttl_seconds"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"sha256"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"a1b2c3..."&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;"data"&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="err"&gt;/*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;actual&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;payload&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&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;"links"&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;"self"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://deunobicho.online/api/..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"evidence"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://deunobicho.online/prova/..."&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;The &lt;code&gt;sha256&lt;/code&gt; in meta lets consumers verify integrity against &lt;code&gt;/api/prova/{id}&lt;/code&gt;. This is not decorative — journalists actually verify it.&lt;/p&gt;




&lt;h2&gt;
  
  
  OpenAPI 3.1 spec
&lt;/h2&gt;

&lt;p&gt;The full spec lives at &lt;code&gt;https://deunobicho.online/api-publica/openapi.json&lt;/code&gt;. A minimal extract:&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;openapi&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;3.1.0&lt;/span&gt;
&lt;span class="na"&gt;info&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;deunobicho Public API&lt;/span&gt;
  &lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;1.0.0&lt;/span&gt;
  &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Read-only Brazilian lottery data&lt;/span&gt;
  &lt;span class="na"&gt;contact&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;Athos Alexandre&lt;/span&gt;
    &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;https://deunobicho.online&lt;/span&gt;
  &lt;span class="na"&gt;license&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;CC BY &lt;/span&gt;&lt;span class="m"&gt;4.0&lt;/span&gt;
&lt;span class="na"&gt;servers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;https://deunobicho.online/api&lt;/span&gt;

&lt;span class="na"&gt;paths&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="s"&gt;/loterias/federal/{date}&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;summary&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Federal lottery result for a specific date&lt;/span&gt;
      &lt;span class="na"&gt;parameters&lt;/span&gt;&lt;span class="pi"&gt;:&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;date&lt;/span&gt;
          &lt;span class="na"&gt;in&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;path&lt;/span&gt;
          &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
          &lt;span class="na"&gt;schema&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;string&lt;/span&gt;
            &lt;span class="na"&gt;format&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;date&lt;/span&gt;
      &lt;span class="na"&gt;responses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;200'&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Result found&lt;/span&gt;
          &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;application/json&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
              &lt;span class="na"&gt;schema&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                &lt;span class="na"&gt;$ref&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;#/components/schemas/FederalResult'&lt;/span&gt;
      &lt;span class="err"&gt;  &lt;/span&gt;&lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;404'&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;No draw on that date&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can drop this into Postman, Swagger UI, or any codegen tool (openapi-generator, orval) and get typed clients in minutes.&lt;/p&gt;




&lt;h2&gt;
  
  
  Integration examples
&lt;/h2&gt;

&lt;h3&gt;
  
  
  JavaScript / TypeScript (with &lt;code&gt;fetch&lt;/code&gt;)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;FederalResult&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;meta&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;generated_at&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;sha256&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="nl"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;concurso&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;data_extracao&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;premios&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;ordem&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;numero&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;valor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt; &lt;span class="p"&gt;}[];&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="nl"&gt;links&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;self&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;evidence&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&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="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;todayFederal&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;FederalResult&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;res&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;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://deunobicho.online/api/loterias/federal/hoje&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;next&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;revalidate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;60&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="nx"&gt;res&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="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Python (with &lt;code&gt;httpx&lt;/code&gt;)
&lt;/h3&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;httpx&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;federal_result&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;date&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;dict&lt;/span&gt;&lt;span class="p"&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;httpx&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="s"&gt;https://deunobicho.online/api/loterias/federal/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;date&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;r&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;r&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;# Fetch and validate integrity
&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;federal_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;2026-09-18&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;assert&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;meta&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;sha256&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Bash / curl (one-liner for scripts)
&lt;/h3&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; https://deunobicho.online/api/loterias/federal/hoje &lt;span class="se"&gt;\&lt;/span&gt;
  | jq &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="s1"&gt;'.data.premios[] | "\(.ordem)º prêmio: \(.numero)"'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1º prêmio: 58437
2º prêmio: 90012
3º prêmio: 21789
4º prêmio: 66412
5º prêmio: 03875
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Use cases in the wild
&lt;/h2&gt;

&lt;p&gt;Users have already built:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A &lt;strong&gt;WhatsApp bot&lt;/strong&gt; (&lt;code&gt;/bicho&lt;/code&gt;) that returns the latest draw when messaged&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;Home Assistant integration&lt;/strong&gt; that announces results at 19:05 daily via Alexa&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;research notebook&lt;/strong&gt; correlating dream-symbol frequency with cultural events&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;journalism project&lt;/strong&gt; verifying alleged bicho manipulation claims&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;university thesis&lt;/strong&gt; on the 134-year statistical properties of the &lt;em&gt;jogo do bicho&lt;/em&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The point is: giving away read-only public data unlocks a long tail of creative uses that no proprietary API would ever discover.&lt;/p&gt;




&lt;h2&gt;
  
  
  Rate limits and terms
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Free tier&lt;/strong&gt;: 300 req/min per IP, 100k req/day&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;License&lt;/strong&gt;: CC BY 4.0 (attribution required, commercial use OK)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Attribution&lt;/strong&gt;: "Data via deunobicho.online" or a link back&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No auth needed&lt;/strong&gt; for the free tier&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No SLA&lt;/strong&gt; — best-effort, but historical uptime is ~99.9% (Vercel edge)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If your project needs guarantees, a dedicated tier with SLA is available on request via &lt;code&gt;contact@deunobicho.online&lt;/code&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  What is intentionally out of scope
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Placing bets.&lt;/strong&gt; This is a &lt;em&gt;data&lt;/em&gt; API, not a betting API. The portal is editorial, not operational.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Predictions / "hot numbers".&lt;/strong&gt; We do not endorse or ship predictive endpoints. Every draw is (assumed) independent.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;User accounts, saved tickets, personalization.&lt;/strong&gt; Free tier is stateless.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Roadmap
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;WebSocket / SSE endpoint for real-time result push&lt;/li&gt;
&lt;li&gt;GraphQL variant (community request, low priority)&lt;/li&gt;
&lt;li&gt;Historical bulk exports (parquet, per-year)&lt;/li&gt;
&lt;li&gt;Multi-language docs (currently EN + PT-BR)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Feature requests welcome via GitHub — the spec repo will be open-sourced shortly.&lt;/p&gt;




&lt;h2&gt;
  
  
  Further reading
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Full OpenAPI spec (browsable)&lt;/strong&gt;: &lt;a href="https://deunobicho.online/api-publica" rel="noopener noreferrer"&gt;deunobicho.online/api-publica&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Portal overview&lt;/strong&gt;: &lt;a href="https://deunobicho.online" rel="noopener noreferrer"&gt;deunobicho.online&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The SHA256 evidence pattern used across the API&lt;/strong&gt;: covered in a separate post on transparency in Brazilian data portals&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Athos Alexandre builds and maintains the deunobicho.online public API. He writes about API design, static site generation, and data transparency on DEV.to.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>api</category>
      <category>openapi</category>
      <category>webdev</category>
      <category>data</category>
    </item>
    <item>
      <title>Using SHA256 to Build Trustworthy Data Portals in Brazil</title>
      <dc:creator>Vou Embarcar</dc:creator>
      <pubDate>Mon, 21 Sep 2026 19:45:46 +0000</pubDate>
      <link>https://dev.to/vou_embarcar_602e285676c8/using-sha256-to-build-trustworthy-data-portals-in-brazil-1k96</link>
      <guid>https://dev.to/vou_embarcar_602e285676c8/using-sha256-to-build-trustworthy-data-portals-in-brazil-1k96</guid>
      <description>&lt;p&gt;Data portals in emerging markets face a specific credibility problem: &lt;strong&gt;how do you prove that the data you display was not silently altered after publication?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This matters more than it sounds. Editorial portals covering lottery results, election counts, or public dataset feeds in Brazil are frequently accused (rightly or not) of tampering. The typical defense — "trust us, we did not change anything" — is not defensible in 2026.&lt;/p&gt;

&lt;p&gt;I run &lt;a href="https://deunobicho.online" rel="noopener noreferrer"&gt;deunobicho.online&lt;/a&gt;, a portal covering Brazilian lottery results, and I want to share how we solved this with a very old, very boring cryptographic primitive: &lt;strong&gt;SHA256 content hashing with public evidence pages&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  The problem in concrete terms
&lt;/h2&gt;

&lt;p&gt;Every day, the portal publishes ~7 lottery result batches. Each batch has:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A publication timestamp&lt;/li&gt;
&lt;li&gt;A source (usually a screenshot of the official state lottery broadcast)&lt;/li&gt;
&lt;li&gt;A tabular payload (numbers, prizes, groups)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If a user visits at 15:30, sees result X, and later returns at 20:00 to find result Y at the same URL, they have a legitimate reason to distrust the portal.&lt;/p&gt;

&lt;p&gt;The naive fix is "just don't change results." Real editorial life is messier: typos happen, source corrections happen, timezones are hard. The correct fix is: &lt;strong&gt;make every change publicly auditable&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  The SHA256 evidence pattern
&lt;/h2&gt;

&lt;p&gt;Here is the pattern we ship:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Every published resource has a canonical JSON representation.&lt;/li&gt;
&lt;li&gt;At publish time, we compute &lt;code&gt;sha256(canonical_json)&lt;/code&gt; and store it alongside the resource.&lt;/li&gt;
&lt;li&gt;We publish a public &lt;em&gt;evidence page&lt;/em&gt; at &lt;code&gt;/prova/&amp;lt;result-id&amp;gt;&lt;/code&gt; that displays:

&lt;ul&gt;
&lt;li&gt;The raw canonical JSON&lt;/li&gt;
&lt;li&gt;The SHA256 hash&lt;/li&gt;
&lt;li&gt;The timestamp&lt;/li&gt;
&lt;li&gt;A link to the source (screenshot / official broadcast)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;The evidence page is served with &lt;code&gt;Cache-Control: public, immutable&lt;/code&gt;. It cannot be modified without changing the URL.&lt;/li&gt;
&lt;li&gt;If we ever correct a result, we publish a new evidence page with a new hash and link both old and new from an audit log.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The user experience: any reader can, at any time, verify that the number they saw on Twitter matches the hash on the portal.&lt;/p&gt;




&lt;h2&gt;
  
  
  Implementation in Next.js 16 (App Router)
&lt;/h2&gt;

&lt;p&gt;Here is the minimal shape of the API route:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// src/app/api/prova/[id]/route.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;createHash&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;node:crypto&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;NextResponse&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;next/server&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;dynamic&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;force-static&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;revalidate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// never revalidate — evidence is immutable&lt;/span&gt;

&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;Evidence&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;published_at&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;   &lt;span class="c1"&gt;// ISO 8601 UTC&lt;/span&gt;
  &lt;span class="nl"&gt;source_url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;     &lt;span class="c1"&gt;// screenshot / broadcast&lt;/span&gt;
  &lt;span class="nl"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Record&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;unknown&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;hash&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;           &lt;span class="c1"&gt;// sha256 hex&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;canonicalize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;unknown&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// stable JSON: sorted keys, no whitespace&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;obj&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;obj&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;object&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;return&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;obj&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="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isArray&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;obj&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;canonicalize&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;,&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&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="nx"&gt;keys&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;obj&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nb"&gt;Record&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;unknown&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;sort&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;{&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;k&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
    &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;k&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nf"&gt;canonicalize&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;obj&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nb"&gt;Record&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;unknown&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="nx"&gt;k&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
  &lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;,&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&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="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;GET&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;_req&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt; &lt;span class="p"&gt;}:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;params&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&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="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;id&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;params&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;record&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;loadRecord&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// from DB&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;canonical&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;canonicalize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;record&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;payload&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;hash&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createHash&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;sha256&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;canonical&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;digest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;hex&lt;/span&gt;&lt;span class="dl"&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;evidence&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Evidence&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;record&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;published_at&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;record&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;published_at&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;source_url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;record&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;source_url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;record&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;hash&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;NextResponse&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="nx"&gt;evidence&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; charset=utf-8&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;Cache-Control&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;public, max-age=31536000, immutable&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="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 key detail is &lt;strong&gt;canonical JSON&lt;/strong&gt;. &lt;code&gt;JSON.stringify&lt;/code&gt; on an object with keys in different orders yields different bytes, hence different SHA256. If you skip canonicalization, your hash is unreliable.&lt;/p&gt;




&lt;h2&gt;
  
  
  Verifying from the client (curl or browser)
&lt;/h2&gt;

&lt;p&gt;A reader can verify independently:&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; https://deunobicho.online/api/prova/2026-09-18-federal-1 &lt;span class="se"&gt;\&lt;/span&gt;
  | jq &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s1"&gt;'.payload'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  | shasum &lt;span class="nt"&gt;-a&lt;/span&gt; 256
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The output should match the &lt;code&gt;hash&lt;/code&gt; field in the same JSON. If it doesn't, someone (us or an intermediate cache) tampered with the payload.&lt;/p&gt;

&lt;p&gt;This is trivially scriptable. We publish a &lt;strong&gt;verify snippet&lt;/strong&gt; on the portal itself, so non-technical journalists can copy-paste and confirm.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why not blockchain?
&lt;/h2&gt;

&lt;p&gt;We get this question every week. The short answer: &lt;strong&gt;you do not need a blockchain to prove content integrity&lt;/strong&gt;. A blockchain proves &lt;em&gt;ordering&lt;/em&gt; and &lt;em&gt;consensus&lt;/em&gt; — properties we do not need. SHA256 + public URL + Wayback Machine snapshots gives:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Content integrity: SHA256 on canonical JSON&lt;/li&gt;
&lt;li&gt;Temporal proof: Wayback Machine crawls (&lt;code&gt;https://web.archive.org/web/*/deunobicho.online/prova/*&lt;/code&gt;) — free, no gas fees, higher trust than most L2s&lt;/li&gt;
&lt;li&gt;Public verifiability: any reader can &lt;code&gt;curl | shasum&lt;/code&gt; in 3 seconds&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A blockchain adds cost, latency, and complexity without adding trust value for this use case. We looked at OpenTimestamps for a while (it &lt;em&gt;is&lt;/em&gt; free and adds a real timestamping anchor to Bitcoin), and we may add it as a secondary anchor later. But the primary evidence is the SHA256 + immutable URL.&lt;/p&gt;




&lt;h2&gt;
  
  
  What we learned in production
&lt;/h2&gt;

&lt;p&gt;Six months in, three lessons:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Immutable URLs are contagious.&lt;/strong&gt; Once we published &lt;code&gt;/prova/&amp;lt;id&amp;gt;&lt;/code&gt; pages, journalists started linking directly to them from their articles. This is now our #1 referrer source. Reader trust compounded.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Canonical JSON is harder than it looks.&lt;/strong&gt; We had a bug where dates were serialized as &lt;code&gt;"2026-09-18T00:00:00.000Z"&lt;/code&gt; in one code path and &lt;code&gt;"2026-09-18T00:00:00Z"&lt;/code&gt; in another. Same date, different bytes, different hash. Fix: normalize all dates to millisecond-precision UTC ISO 8601 at write time.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Corrections happen. Plan for them.&lt;/strong&gt; We had to correct one result in six months. The audit trail worked: we published &lt;code&gt;/prova/&amp;lt;id&amp;gt;-v2&lt;/code&gt; with a new hash, added a &lt;code&gt;superseded_by&lt;/code&gt; field to the old evidence page, and posted a public correction note. Zero credibility damage — the transparency actually &lt;em&gt;increased&lt;/em&gt; trust.&lt;/p&gt;




&lt;h2&gt;
  
  
  Reading the source
&lt;/h2&gt;

&lt;p&gt;The full implementation of &lt;code&gt;/prova/&amp;lt;id&amp;gt;&lt;/code&gt; is open on the portal itself — visit any recent result and scroll down to the "Prova SHA256" footer. The API is documented at &lt;a href="https://deunobicho.online/api-publica" rel="noopener noreferrer"&gt;deunobicho.online/api-publica&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;For portal maintainers building similar infra in other regulated / high-scrutiny domains (elections, public health data, financial disclosures), the same pattern generalizes:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Canonical JSON of the payload&lt;/li&gt;
&lt;li&gt;SHA256 at publish time&lt;/li&gt;
&lt;li&gt;Immutable URL for the evidence&lt;/li&gt;
&lt;li&gt;Wayback Machine as temporal anchor&lt;/li&gt;
&lt;li&gt;Public audit log for corrections&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That is it. No blockchain, no proprietary vendor, no trust in the portal itself — just cryptographic integrity readers can verify with &lt;code&gt;shasum -a 256&lt;/code&gt;.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Athos Alexandre is the founder of &lt;a href="https://deunobicho.online" rel="noopener noreferrer"&gt;deunobicho.online&lt;/a&gt;, an editorial portal covering Brazilian lottery data. He writes about data transparency, Next.js, and building trustworthy public interfaces in high-scrutiny domains.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>security</category>
      <category>nextjs</category>
      <category>cryptography</category>
    </item>
    <item>
      <title>Using SHA256 to Build Trustworthy Data Portals in Brazil</title>
      <dc:creator>Vou Embarcar</dc:creator>
      <pubDate>Mon, 21 Sep 2026 19:01:16 +0000</pubDate>
      <link>https://dev.to/vou_embarcar_602e285676c8/using-sha256-to-build-trustworthy-data-portals-in-brazil-3629</link>
      <guid>https://dev.to/vou_embarcar_602e285676c8/using-sha256-to-build-trustworthy-data-portals-in-brazil-3629</guid>
      <description>&lt;p&gt;Data portals in emerging markets face a specific credibility problem: &lt;strong&gt;how do you prove that the data you display was not silently altered after publication?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This matters more than it sounds. Editorial portals covering lottery results, election counts, or public dataset feeds in Brazil are frequently accused (rightly or not) of tampering. The typical defense — "trust us, we did not change anything" — is not defensible in 2026.&lt;/p&gt;

&lt;p&gt;I run &lt;a href="https://deunobicho.online" rel="noopener noreferrer"&gt;deunobicho.online&lt;/a&gt;, a portal covering Brazilian lottery results, and I want to share how we solved this with a very old, very boring cryptographic primitive: &lt;strong&gt;SHA256 content hashing with public evidence pages&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  The problem in concrete terms
&lt;/h2&gt;

&lt;p&gt;Every day, the portal publishes ~7 lottery result batches. Each batch has:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A publication timestamp&lt;/li&gt;
&lt;li&gt;A source (usually a screenshot of the official state lottery broadcast)&lt;/li&gt;
&lt;li&gt;A tabular payload (numbers, prizes, groups)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If a user visits at 15:30, sees result X, and later returns at 20:00 to find result Y at the same URL, they have a legitimate reason to distrust the portal.&lt;/p&gt;

&lt;p&gt;The naive fix is "just don't change results." Real editorial life is messier: typos happen, source corrections happen, timezones are hard. The correct fix is: &lt;strong&gt;make every change publicly auditable&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  The SHA256 evidence pattern
&lt;/h2&gt;

&lt;p&gt;Here is the pattern we ship:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Every published resource has a canonical JSON representation.&lt;/li&gt;
&lt;li&gt;At publish time, we compute &lt;code&gt;sha256(canonical_json)&lt;/code&gt; and store it alongside the resource.&lt;/li&gt;
&lt;li&gt;We publish a public &lt;em&gt;evidence page&lt;/em&gt; at &lt;code&gt;/prova/&amp;lt;result-id&amp;gt;&lt;/code&gt; that displays:

&lt;ul&gt;
&lt;li&gt;The raw canonical JSON&lt;/li&gt;
&lt;li&gt;The SHA256 hash&lt;/li&gt;
&lt;li&gt;The timestamp&lt;/li&gt;
&lt;li&gt;A link to the source (screenshot / official broadcast)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;The evidence page is served with &lt;code&gt;Cache-Control: public, immutable&lt;/code&gt;. It cannot be modified without changing the URL.&lt;/li&gt;
&lt;li&gt;If we ever correct a result, we publish a new evidence page with a new hash and link both old and new from an audit log.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The user experience: any reader can, at any time, verify that the number they saw on Twitter matches the hash on the portal.&lt;/p&gt;




&lt;h2&gt;
  
  
  Implementation in Next.js 16 (App Router)
&lt;/h2&gt;

&lt;p&gt;Here is the minimal shape of the API route:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// src/app/api/prova/[id]/route.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;createHash&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;node:crypto&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;NextResponse&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;next/server&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;dynamic&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;force-static&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;revalidate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// never revalidate — evidence is immutable&lt;/span&gt;

&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;Evidence&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;published_at&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;   &lt;span class="c1"&gt;// ISO 8601 UTC&lt;/span&gt;
  &lt;span class="nl"&gt;source_url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;     &lt;span class="c1"&gt;// screenshot / broadcast&lt;/span&gt;
  &lt;span class="nl"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Record&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;unknown&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;hash&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;           &lt;span class="c1"&gt;// sha256 hex&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;canonicalize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;unknown&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// stable JSON: sorted keys, no whitespace&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;obj&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;obj&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;object&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;return&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;obj&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="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isArray&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;obj&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;canonicalize&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;,&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&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="nx"&gt;keys&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;obj&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nb"&gt;Record&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;unknown&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;sort&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;{&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;k&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
    &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;k&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nf"&gt;canonicalize&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;obj&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nb"&gt;Record&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;unknown&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="nx"&gt;k&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
  &lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;,&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&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="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;GET&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;_req&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt; &lt;span class="p"&gt;}:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;params&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&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="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;id&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;params&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;record&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;loadRecord&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// from DB&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;canonical&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;canonicalize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;record&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;payload&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;hash&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createHash&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;sha256&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;canonical&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;digest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;hex&lt;/span&gt;&lt;span class="dl"&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;evidence&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Evidence&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;record&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;published_at&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;record&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;published_at&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;source_url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;record&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;source_url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;record&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;hash&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;NextResponse&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="nx"&gt;evidence&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; charset=utf-8&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;Cache-Control&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;public, max-age=31536000, immutable&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="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 key detail is &lt;strong&gt;canonical JSON&lt;/strong&gt;. &lt;code&gt;JSON.stringify&lt;/code&gt; on an object with keys in different orders yields different bytes, hence different SHA256. If you skip canonicalization, your hash is unreliable.&lt;/p&gt;




&lt;h2&gt;
  
  
  Verifying from the client (curl or browser)
&lt;/h2&gt;

&lt;p&gt;A reader can verify independently:&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; https://deunobicho.online/api/prova/2026-09-18-federal-1 &lt;span class="se"&gt;\&lt;/span&gt;
  | jq &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s1"&gt;'.payload'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  | shasum &lt;span class="nt"&gt;-a&lt;/span&gt; 256
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The output should match the &lt;code&gt;hash&lt;/code&gt; field in the same JSON. If it doesn't, someone (us or an intermediate cache) tampered with the payload.&lt;/p&gt;

&lt;p&gt;This is trivially scriptable. We publish a &lt;strong&gt;verify snippet&lt;/strong&gt; on the portal itself, so non-technical journalists can copy-paste and confirm.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why not blockchain?
&lt;/h2&gt;

&lt;p&gt;We get this question every week. The short answer: &lt;strong&gt;you do not need a blockchain to prove content integrity&lt;/strong&gt;. A blockchain proves &lt;em&gt;ordering&lt;/em&gt; and &lt;em&gt;consensus&lt;/em&gt; — properties we do not need. SHA256 + public URL + Wayback Machine snapshots gives:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Content integrity: SHA256 on canonical JSON&lt;/li&gt;
&lt;li&gt;Temporal proof: Wayback Machine crawls (&lt;code&gt;https://web.archive.org/web/*/deunobicho.online/prova/*&lt;/code&gt;) — free, no gas fees, higher trust than most L2s&lt;/li&gt;
&lt;li&gt;Public verifiability: any reader can &lt;code&gt;curl | shasum&lt;/code&gt; in 3 seconds&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A blockchain adds cost, latency, and complexity without adding trust value for this use case. We looked at OpenTimestamps for a while (it &lt;em&gt;is&lt;/em&gt; free and adds a real timestamping anchor to Bitcoin), and we may add it as a secondary anchor later. But the primary evidence is the SHA256 + immutable URL.&lt;/p&gt;




&lt;h2&gt;
  
  
  What we learned in production
&lt;/h2&gt;

&lt;p&gt;Six months in, three lessons:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Immutable URLs are contagious.&lt;/strong&gt; Once we published &lt;code&gt;/prova/&amp;lt;id&amp;gt;&lt;/code&gt; pages, journalists started linking directly to them from their articles. This is now our #1 referrer source. Reader trust compounded.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Canonical JSON is harder than it looks.&lt;/strong&gt; We had a bug where dates were serialized as &lt;code&gt;"2026-09-18T00:00:00.000Z"&lt;/code&gt; in one code path and &lt;code&gt;"2026-09-18T00:00:00Z"&lt;/code&gt; in another. Same date, different bytes, different hash. Fix: normalize all dates to millisecond-precision UTC ISO 8601 at write time.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Corrections happen. Plan for them.&lt;/strong&gt; We had to correct one result in six months. The audit trail worked: we published &lt;code&gt;/prova/&amp;lt;id&amp;gt;-v2&lt;/code&gt; with a new hash, added a &lt;code&gt;superseded_by&lt;/code&gt; field to the old evidence page, and posted a public correction note. Zero credibility damage — the transparency actually &lt;em&gt;increased&lt;/em&gt; trust.&lt;/p&gt;




&lt;h2&gt;
  
  
  Reading the source
&lt;/h2&gt;

&lt;p&gt;The full implementation of &lt;code&gt;/prova/&amp;lt;id&amp;gt;&lt;/code&gt; is open on the portal itself — visit any recent result and scroll down to the "Prova SHA256" footer. The API is documented at &lt;a href="https://deunobicho.online/api-publica" rel="noopener noreferrer"&gt;deunobicho.online/api-publica&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;For portal maintainers building similar infra in other regulated / high-scrutiny domains (elections, public health data, financial disclosures), the same pattern generalizes:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Canonical JSON of the payload&lt;/li&gt;
&lt;li&gt;SHA256 at publish time&lt;/li&gt;
&lt;li&gt;Immutable URL for the evidence&lt;/li&gt;
&lt;li&gt;Wayback Machine as temporal anchor&lt;/li&gt;
&lt;li&gt;Public audit log for corrections&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That is it. No blockchain, no proprietary vendor, no trust in the portal itself — just cryptographic integrity readers can verify with &lt;code&gt;shasum -a 256&lt;/code&gt;.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Athos Alexandre is the founder of &lt;a href="https://deunobicho.online" rel="noopener noreferrer"&gt;deunobicho.online&lt;/a&gt;, an editorial portal covering Brazilian lottery data. He writes about data transparency, Next.js, and building trustworthy public interfaces in high-scrutiny domains.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>security</category>
      <category>nextjs</category>
      <category>cryptography</category>
    </item>
  </channel>
</rss>
