<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Quiet Builds]]></title><description><![CDATA[Quiet Builds]]></description><link>https://quietbuilds.hashnode.dev</link><image><url>https://cdn.hashnode.com/res/hashnode/image/upload/v1593680282896/kNC7E8IR4.png</url><title>Quiet Builds</title><link>https://quietbuilds.hashnode.dev</link></image><generator>RSS for Node</generator><lastBuildDate>Thu, 15 Oct 2026 07:27:44 GMT</lastBuildDate><atom:link href="https://quietbuilds.hashnode.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[I Shipped an MCP Server in 4 Hours for a Side Project That Has 690 Sessions a Week
]]></title><description><![CDATA[The Model Context Protocol is an emerging standard for agent-readable APIs. A read-only MCP server is the cheapest agent-discovery surface a small project can build right now.
I Shipped an MCP Server ]]></description><link>https://quietbuilds.hashnode.dev/i-shipped-an-mcp-server-in-4-hours-for-a-side-project-that-has-690-sessions-a-week</link><guid isPermaLink="true">https://quietbuilds.hashnode.dev/i-shipped-an-mcp-server-in-4-hours-for-a-side-project-that-has-690-sessions-a-week</guid><category><![CDATA[mcp]]></category><category><![CDATA[AI]]></category><category><![CDATA[#anthropic]]></category><dc:creator><![CDATA[Elena]]></dc:creator><pubDate>Thu, 09 Jul 2026 08:47:13 GMT</pubDate><content:encoded><![CDATA[<p><em>The Model Context Protocol is an emerging standard for agent-readable APIs. A read-only MCP server is the cheapest agent-discovery surface a small project can build right now.</em></p>
<p>I Shipped an MCP Server in 4 Hours for a Side Project That Has 690 Sessions a Week.</p>
<h3>Here Is What I Built and Why.</h3>
<p>I run a free browser-based passport photo tool that does about 690 sessions a week, growing. Most of the traffic comes from ChatGPT (<del>65 percent) and from “direct” sources that are mostly hidden ChatGPT mobile referrer (</del>20 percent). Google sends about 6 percent. The funny thing about that distribution is that it does not look like a 2010s SEO win. It looks like the start of a new pattern: AI engines are now the dominant traffic source for tools that solve crisp factual questions. Last week I shipped an MCP server for the same project. Total work: about four hours. Total expected immediate users: under a hundred. So why did I bother. This is the post about that decision. It might be useful for any indie operator deciding whether to invest in agent-readable surfaces right now or wait six months.</p>
<h3>What MCP actually is</h3>
<p>Model Context Protocol (MCP) is Anthropic’s open spec for connecting AI agents to external tools and data sources. It is a JSON-RPC 2.0 contract that defines three core methods: <code>initialize</code>, <code>tools/list</code>, and <code>tools/call</code>. Any agent that speaks MCP can connect to any MCP server and get a fresh tool surface that the model knows how to call. The clients that support it as of mid-2026: Claude Desktop, Cursor, Continue, the Anthropic SDK agent loop, and a growing list of custom frameworks. The user base is small (Claude Desktop has roughly one million users, Cursor about five hundred thousand, Continue under one hundred thousand) but the user is high-intent: someone who installed an AI desktop client to use it as a daily workflow tool. For a small project, the question is whether the MCP user base is worth the build effort. The answer for me turned out to be yes, but not for the reasons I expected.</p>
<h3>The four reasons it was worth shipping</h3>
<p>✔︎ One. The build cost was almost nothing. I already had a public REST API endpoint at /api/specs that returns country photo specifications as JSON. The MCP server reuses the same data, wrapped in JSON-RPC 2.0 with four tool definitions. Total new code: about 350 lines split across a route handler and a tools file. No new infrastructure, no new database, no new deployment target. The MCP server lives at the same domain, hosted on the same Vercel project, alongside the REST API and the website.</p>
<p>✔︎ Two. It is a citation surface.** MCP servers get listed in directories like glama.ai (30,000 plus servers indexed), awesome-mcp lists on GitHub, smithery.ai. Each listing is a backlink to my project’s docs page. The MCP page itself carries WebAPI Schema.org markup so AI engines that index API surfaces (Google AI Overview, Bing Copilot) recognize the endpoint as a registered API. None of these are big traffic individually. Collectively they signal authority to AI-search ranking systems.</p>
<p>✔︎ Three. It is a content piece on its own. This post you are reading exists because the MCP server exists. The MCP shipment is the narrative hook. Without it, I would not have written this essay, would not have a new technical artifact to point at on Dev.to, would not have a reason to ping the IH community. Content compounds on tangible shipments more than on opinions.</p>
<p>✔︎ Four. First-mover advantage in a narrow niche. A WebSearch for “passport photo MCP server” returned zero results before I shipped. After shipping, this project is the canonical answer for that query pattern. When MCP adoption grows, narrow niches with an existing answer get cited first.</p>
<h3>What I actually built Four read-only tools:</h3>
<ul>
<li><p><code>list_supported_countries</code> enumerates the 100 plus countries with passport and visa photo specs.</p>
</li>
<li><p><code>get_visa_photo_spec</code> takes a country and optional document type, returns dimensions in mm and pixels, background color, full requirements list, and the URL where the user can generate the photo.</p>
</li>
<li><p><code>get_portal_constraints</code> takes a government portal id and returns file-size and format constraints (DS-160 240 KB cap, Saudi Enjaz 200 KB, India Sarathi 20–50 KB window, China COVA 40 KB to 1 MB JPEG, etc).</p>
</li>
<li><p><code>get_photo_tool_url</code> returns the deep link to the browser tool for a country. All read-only. The tools return metadata. They do not process photos. Photo handling stays in the browser tool itself, where the photo never touches a server (Brian Acton would approve). This read-only design was deliberate. The temptation with an MCP server is to expose write operations, because then agents can do more. But write operations on biometric data trigger GDPR Article 9, Illinois BIPA, India DPDP Act 2023, and a few state-level US biometric laws. The privacy exposure becomes meaningful as soon as the server can receive a photo. I designed around that by keeping the MCP layer metadata-only and leaving the image handling in the original browser-only pipeline. The agent can answer “what spec do I need” and “where do I go to generate it” without ever touching the user’s photo.</p>
</li>
</ul>
<h3>What the actual build looked like</h3>
<p>Hour one. Designed the tools. Wrote out the four tool definitions on paper before opening an editor. The decisions were: which questions does an agent need to answer, what is the minimum input it needs from the user, what is the output shape that the model can format into a natural-language response without further processing. Get Elena Dev’s stories in your inbox Join Medium for free to get updates from this writer.</p>
<p>Hour two. Implemented <code>lib/mcpTools.ts</code>. Each tool is a function that takes a typed args object and returns a <code>ToolResult</code> with <code>content: Array&lt;{type: ‘text’, text: string}&gt;</code>. The TOOLS array exports the JSON Schema for each tool. The callTool dispatcher routes by name. No imports beyond the existing country data module.</p>
<p>Hour three. Implemented the JSON-RPC 2.0 endpoint at <code>app/api/mcp/route.ts</code>. Three methods: <code>initialize</code> returns server capabilities, <code>tools/list</code> returns the TOOLS array, <code>tools/call</code> dispatches to <code>callTool</code>. CORS open. No auth. Total endpoint code: about 140 lines including header comments. The GET handler returns server metadata for discovery clients that browse for MCP servers.</p>
<p>Hour four. Wrote the <code>/developers/mcp</code> docs page. Schema.org TechArticle plus WebAPI markup so AI search engines recognize it. Setup instructions for Claude Desktop, Cursor, and Continue. Direct JSON-RPC examples for custom agents. Design principles, FAQ, related resources. Done. Pushed to main, Vercel built it, the endpoint went live.</p>
<h3>How I tested it</h3>
<p>Three steps, ten minutes:</p>
<ul>
<li><p><code>curl -X POST https://idphotosnap.com/api/mcp -H “Content-Type: application/json” -d ‘{“jsonrpc”:”2.0",”id”:1,”method”:”initialize”,”params”:{}}’</code>. Server returned protocol handshake. Good.</p>
</li>
<li><p><code>curl -X POST</code> with <code>method: “tools/list”</code>. Server returned the four tools with schemas. Good.</p>
</li>
<li><p>Added the server to Claude Desktop config, restarted, asked “what size is a Schengen visa photo.” Claude called <code>get_visa_photo_spec(country: “schengen”)</code> and answered with the correct 35 by 45 mm light grey spec. Verified end to end. No edge cases. No retry logic needed. JSON-RPC 2.0 is a small protocol and the implementation is bounded.</p>
</li>
</ul>
<h3>What I will not do</h3>
<p>I will not add write operations. The privacy positioning of the browser tool depends on the photo never reaching a server. If the MCP server starts accepting image data, the entire architectural story collapses. The MCP layer stays read-only. Users who want to generate a photo follow the URL the tool returns. I will not add authentication. The dataset is already MIT open data on GitHub, npm, PyPI, crates.io, Hugging Face. Authenticating access to a derivative API would just create friction without protecting anything that is not already public. I will not maintain protocol parity with every emerging agent framework. MCP is the Anthropic surface. OpenAI has Custom GPTs with OpenAPI Actions, which I support via a separate OpenAPI spec at the same domain. Both contracts wrap the same underlying data. If Google or Mistral ship a third protocol, I will consider it after a year of usage data.</p>
<h3>Was four hours worth it</h3>
<p>Three days in, the answer looks like yes. Three MCP awesome lists have accepted PRs adding our entry. Glama.ai indexed the server within twenty-four hours of submission. The /developers/mcp docs page got picked up by Bing within the same window. The Custom GPT version of the same data, published to the OpenAI registry the same week, is showing initial discovery traffic. None of this individually moves the traffic needle. But the cumulative pattern is that every agent-readable surface I add now is a placeholder for whatever the dominant agent framework looks like in twelve months. The cost to participate is one half-day per channel. The cost of being absent when adoption curves steepen is much harder to model.</p>
<h3>What you can take from this</h3>
<p>Build a JSON-RPC 2.0 endpoint that wraps your existing public data. Document it. Submit to three directories. Move on. Total cost is a workday. The downside if MCP fizzles is one wasted day. The upside if it does not is being in the early-mover set when AI agents become the default workflow client. If your data is not public yet, do that first. An open dataset is the actual moat. The MCP server is a thin layer on top of one. If your project is not a side project but a serious business, the calculation is the same just at different absolute numbers. The four hours scale to a sprint, and the upside is bigger because the user base is more valuable per agent install. Either way: the cheapest option is not zero. Zero ages badly.</p>
<p>#anthropic #MCP #startup #indiehacking</p>
]]></content:encoded></item><item><title><![CDATA[I Open-Sourced Passport Photo Specs for 100 Countries (MIT, JSON)]]></title><description><![CDATA[I Open-Sourced Passport Photo Specs for 100 Countries (MIT, JSON)
If you have ever built a passport photo tool, you know the most tedious part is not the face detection or the background segmentation.]]></description><link>https://quietbuilds.hashnode.dev/i-open-sourced-passport-photo-specs-for-100-countries-mit-json</link><guid isPermaLink="true">https://quietbuilds.hashnode.dev/i-open-sourced-passport-photo-specs-for-100-countries-mit-json</guid><dc:creator><![CDATA[Elena]]></dc:creator><pubDate>Wed, 13 May 2026 15:21:58 GMT</pubDate><content:encoded><![CDATA[<h1>I Open-Sourced Passport Photo Specs for 100 Countries (MIT, JSON)</h1>
<p>If you have ever built a passport photo tool, you know the most tedious part is not the face detection or the background segmentation. It is the country specifications. Every country has a slightly different size. Different background color requirements. Different file size caps on the online portal. A different head-height percentage. And the documentation is scattered across consulate websites in 12 languages.</p>
<p>I have been collecting and validating these specs for a year while building <a href="https://idphotosnap.com">IDPhotoSnap</a>, a free browser-only passport photo tool. Last week I published the dataset as an open repository at <a href="https://github.com/whitetirocket/passport-photo-specs">github.com/whitetirocket/passport-photo-specs</a> - MIT licensed, JSON and TypeScript and Python bindings, 100 countries, 248 document formats.</p>
<p>This post is about why I did it, what is in it, and how you can use it in your own tool or research.</p>
<h2>The problem with passport photo specs</h2>
<p>Most photo tools claim to "support 200+ countries" or "900+ document types". When you check what that actually means, two patterns appear:</p>
<ol>
<li><p>They have one default spec (35x45mm, white background, ICAO 9303) and apply it as a fallback for every country that has not been independently validated. So "200 countries" is really "30 countries with verified specs plus 170 countries getting the default".</p>
</li>
<li><p>They scrape specs from each other rather than from the original government source. When a country updates its requirements (which happens every few years), the updated spec takes 6-12 months to propagate through the ecosystem.</p>
</li>
</ol>
<p>For a passport photo this matters. A wrong photo gets rejected at the consulate, and the application gets delayed by days or weeks. The user paid \(15 for the photo at a drugstore and another \)200 for the visa application, and the photo is the part that fails.</p>
<p>I wanted a single source of truth that I could verify against government sources directly.</p>
<h2>What is in the dataset</h2>
<p>Each country entry has documents (passport, visa, ID card, driving licence, residence permit, sometimes more). Each document has:</p>
<ul>
<li><p><code>widthMm</code>, <code>heightMm</code> - print dimensions in mm</p>
</li>
<li><p><code>widthPx</code>, <code>heightPx</code> - pixel dimensions at the given DPI</p>
</li>
<li><p><code>dpi</code> - typically 300</p>
</li>
<li><p><code>background</code> - human-readable description ("Plain white", "Light grey")</p>
</li>
<li><p><code>bgColor</code> - hex code</p>
</li>
<li><p><code>bgColorLabel</code> - short label for UI</p>
</li>
<li><p><code>requirements</code> - bullet list of full requirements</p>
</li>
</ul>
<p>Example for an Italian Schengen visa photo:</p>
<pre><code class="language-json">{
  "id": "italy-visa",
  "name": "Visa",
  "slug": "italy-visa-photo",
  "widthMm": 35,
  "heightMm": 45,
  "widthPx": 413,
  "heightPx": 531,
  "dpi": 300,
  "background": "Plain light grey",
  "bgColor": "#eeeeee",
  "bgColorLabel": "Light grey",
  "requirements": ["Plain light grey background", "Head 70-80% of frame", ...]
}
</code></pre>
<p>The repository ships:</p>
<ul>
<li><p><code>specs/specs.json</code> - the full dataset (160 KB)</p>
</li>
<li><p><code>specs/index.ts</code> - TypeScript module with typed exports and lookup helpers</p>
</li>
<li><p><code>specs/__init__.py</code> - Python module with the same API</p>
</li>
<li><p><code>scripts/validate.js</code> - integrity check that runs on every commit</p>
</li>
</ul>
<h2>How to use it</h2>
<h3>TypeScript / JavaScript</h3>
<pre><code class="language-ts">import { findDocument, findCountry, countries } from 'passport-photo-specs'

const italyVisa = findDocument('italy-visa-photo')
// { widthMm: 35, heightMm: 45, widthPx: 413, heightPx: 531, dpi: 300,
//   background: 'Plain light grey', bgColor: '#eeeeee', ... }

const india = findCountry('india')
// All India documents: passport, visa, OCI, PAN card, PCC, driving licence, voter ID
</code></pre>
<h3>Python</h3>
<pre><code class="language-python">from passport_photo_specs import find_document, countries

italy_visa = find_document('italy-visa-photo')
print(f"Size: {italy_visa['widthMm']}x{italy_visa['heightMm']}mm")
print(f"Background: {italy_visa['background']}")
</code></pre>
<h3>Plain JSON</h3>
<p>If you are not on the JS or Python stack, load <code>specs/specs.json</code> in any language. The structure is documented in the README.</p>
<h2>Notable specs that catch tool builders</h2>
<p>These are commonly mis-specified across the ecosystem. If you are building something in this category, double-check yours:</p>
<ul>
<li><p><strong>Chinese visa is 33x48mm</strong>, not 35x45 like Schengen and not square like US. It is the only major format with this aspect ratio.</p>
</li>
<li><p><strong>Chinese visa COVA portal</strong> requires file size 40 KB to 1 MB, JPG only, 354x472 px minimum.</p>
</li>
<li><p><strong>US DS-160 visa upload caps files at 240 KB</strong>. Square 600x600 minimum, 1200x1200 maximum.</p>
</li>
<li><p><strong>Indian Sarathi driving licence requires 20-50 KB</strong> - a <em>window</em>, not a max. Files under 20 KB and over 50 KB both fail silently.</p>
</li>
<li><p><strong>Indian PAN card is 25x35mm</strong> at 200x230 px maximum, 10-300 KB. Smaller than most other photos.</p>
</li>
<li><p><strong>UK passport accepts light grey background</strong> in addition to white. One of the few that does. Glasses not allowed since 2018.</p>
</li>
<li><p><strong>German Personalausweis</strong> has a May 2025 rule requiring digital-only photos via Buergeramt terminal or certified Fotograf for German citizens. This is separate from the German Schengen visa for foreign applicants, which still accepts standard 35x45mm digital photos.</p>
</li>
</ul>
<p>These five entries are the dataset's biggest non-obvious wins. If your tool gets these right, applicants from those use cases will not have their photos rejected.</p>
<h2>Validation methodology</h2>
<p>For each country, I checked at least one of these sources:</p>
<ul>
<li><p>US Department of State (travel.state.gov)</p>
</li>
<li><p>UK HM Passport Office (gov.uk/photos-for-passports)</p>
</li>
<li><p>German Bundesdruckerei + Auswaertiges Amt</p>
</li>
<li><p>Italian Polizia di Stato + Questura</p>
</li>
<li><p>French ANTS (ants.gouv.fr)</p>
</li>
<li><p>Spanish Ministerio de Asuntos Exteriores</p>
</li>
<li><p>Canadian IRCC (Immigration, Refugees and Citizenship Canada)</p>
</li>
<li><p>Australian Department of Foreign Affairs and Trade</p>
</li>
<li><p>Indian Passport Seva Kendra (passportindia.gov.in)</p>
</li>
<li><p>Indian RTO / Sarathi / Parivahan portals</p>
</li>
<li><p>Chinese Ministry of Foreign Affairs (cova.cs.mfa.gov.cn)</p>
</li>
<li><p>Japanese Ministry of Foreign Affairs (mofa.go.jp)</p>
</li>
<li><p>Schengen visa code Annex 11 (ICAO 9303)</p>
</li>
<li><p>EU Entry/Exit System (EES) specifications</p>
</li>
<li><p>New Zealand DIA (passports.govt.nz)</p>
</li>
<li><p>Brazilian Polícia Federal</p>
</li>
<li><p>Mexican SRE (gob.mx)</p>
</li>
</ul>
<p>Beyond top-20 countries, I follow the ICAO 9303 default unless an official source documents a country-specific deviation. I do not pad the dataset with synthetic country variants - if a country's photo spec is identical to ICAO 9303 default, it is marked as such rather than counted as "unique".</p>
<p>This means the 100 country count is honest. I could have called it 193 (all ICAO member states) and claimed the same coverage as competitors. I chose not to.</p>
<h2>Why open source</h2>
<p>Passport photo specs are public information from public government sources. There is no business reason to keep them proprietary, and the world is better served by having one canonical machine-readable dataset than by every photo tool re-researching them in silos.</p>
<p>There is also a research angle. If you are working on biometric privacy, immigration policy data, or AI training corpora that need to recognize valid versus invalid ID photos, the dataset is now available without a license fee or per-row pricing.</p>
<h2>What I would like</h2>
<p>If you find a spec that is wrong, open an issue with a link to the official government source. If you want to add a country I do not have, open a PR with the spec plus the citation.</p>
<p>I will not accept specs copied from other photo tool aggregators - we have enough of those. Government sources only.</p>
<h2>Used in production</h2>
<p>The dataset powers <a href="https://idphotosnap.com">IDPhotoSnap</a>, the free browser-only passport photo tool I mentioned at the start. The tool does face detection in <code>face-api.js</code>, background segmentation in <code>BRIA RMBG-1.4</code> (ONNX), cropping and JPEG export in Canvas API. The full pipeline runs in WebAssembly - the photo never leaves the user's device. The country specs from this dataset are what drives the per-country crop dimensions, background color replacement, and file size targeting for online portal uploads.</p>
<p>If you build something else with the dataset, open a PR to add yourself to the "Used by" section in the README.</p>
<h2>Repository links</h2>
<ul>
<li><p>Repository: <a href="https://github.com/whitetirocket/passport-photo-specs">github.com/whitetirocket/passport-photo-specs</a></p>
</li>
<li><p>Direct JSON: <a href="https://github.com/whitetirocket/passport-photo-specs/blob/main/specs/specs.json">specs/specs.json</a></p>
</li>
<li><p>Issues for spec corrections: <a href="https://github.com/whitetirocket/passport-photo-specs/issues">github.com/whitetirocket/passport-photo-specs/issues</a></p>
</li>
<li><p>Live demo: <a href="https://idphotosnap.com">idphotosnap.com</a></p>
</li>
</ul>
<p>MIT licensed. Attribution appreciated but not required.</p>
<p>#opensource, #webdev, #javascript, #typescript, #privacy</p>
]]></content:encoded></item><item><title><![CDATA[Passport photo specifications: a country-by-country technical reference]]></title><description><![CDATA[If you have ever tried to programmatically generate or validate a passport photo, you have likely run into the same wall I did: every country has its own specifications, and many of them are documented in PDFs scattered across consulate websites in t...]]></description><link>https://quietbuilds.hashnode.dev/passport-photo-specifications-a-country-by-country-technical-reference</link><guid isPermaLink="true">https://quietbuilds.hashnode.dev/passport-photo-specifications-a-country-by-country-technical-reference</guid><category><![CDATA[JavaScript]]></category><category><![CDATA[Tutorial]]></category><category><![CDATA[webdev]]></category><dc:creator><![CDATA[Elena]]></dc:creator><pubDate>Tue, 28 Apr 2026 06:44:40 GMT</pubDate><content:encoded><![CDATA[<p>If you have ever tried to programmatically generate or validate a passport photo, you have likely run into the same wall I did: every country has its own specifications, and many of them are documented in PDFs scattered across consulate websites in the local language.</p>
<p>This post collects the key technical specifications for the major countries, in one place, in a format you can encode into a config file or validation function.</p>
<h2 id="heading-what-every-passport-photo-spec-actually-contains">What every passport photo spec actually contains</h2>
<p>There are five fields you need:</p>
<ol>
<li><strong>Output dimensions</strong> — physical size in millimeters and DPI for printing.</li>
<li><strong>Face height percentage</strong> — the vertical fraction of the photo that should be occupied by the face, measured from chin to crown.</li>
<li><strong>Vertical center</strong> — where the eyes (or the midpoint of the head) should sit vertically in the frame.</li>
<li><strong>Background</strong> — required color, usually expressed as a target shade.</li>
<li><strong>Expression</strong> — almost universally neutral, mouth closed.</li>
</ol>
<h2 id="heading-example-encoded-specifications">Example: encoded specifications</h2>
<p>Here is how the same specifications look as a JSON config:</p>
<h2 id="heading-quick-reference-for-the-major-countries">Quick reference for the major countries</h2>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Country</td><td>Size (mm)</td><td>Face %</td><td>Background</td></tr>
</thead>
<tbody>
<tr>
<td>United States</td><td>51 x 51</td><td>50-69%</td><td>White</td></tr>
<tr>
<td>United Kingdom</td><td>35 x 45</td><td>50-75%</td><td>Off-white / light grey</td></tr>
<tr>
<td>Schengen / EU</td><td>35 x 45</td><td>70-80%</td><td>White</td></tr>
<tr>
<td>Canada</td><td>50 x 70</td><td>31 mm chin-to-crown</td><td>White</td></tr>
<tr>
<td>India</td><td>51 x 51</td><td>70-80%</td><td>White</td></tr>
<tr>
<td>Australia</td><td>35 x 45</td><td>70-80%</td><td>Light grey or white</td></tr>
<tr>
<td>Japan</td><td>35 x 45</td><td>70-80%</td><td>White</td></tr>
<tr>
<td>China</td><td>33 x 48</td><td>70-80%</td><td>White (specific shade)</td></tr>
</tbody>
</table>
</div><h2 id="heading-where-the-specs-come-from">Where the specs come from</h2>
<p>Most of these specifications trace back to ICAO 9303, the international standard for machine-readable travel documents. Countries deviate from ICAO in two main ways:</p>
<ul>
<li><strong>Background color</strong> — Finland uses light grey, Indonesia historically used red, India uses white.</li>
<li><strong>Face size</strong> — the US allows a smaller face (50-69%) than most of Europe (70-80%).</li>
</ul>
<p>For the long tail of countries not on this list, the most reliable sources are:</p>
<ol>
<li>The countries embassy or consulate website</li>
<li>The ICAO 9303 specification as a default</li>
<li>Real-world rejection reports (StackExchange Travel, immigration forums)</li>
</ol>
<h2 id="heading-why-this-is-harder-than-it-should-be">Why this is harder than it should be</h2>
<p>The technical work — cropping an image to a target size with face detection — is straightforward. The actual hard work is curating the spec database. Some countries publish clear PDFs. Others have vague text. Some require specific paper types or photo finishing that frankly cannot be enforced via software.</p>
<p>If you are building anything in this space, I would strongly recommend treating the spec database as your primary asset, not the image processing code. The processing code is a few hundred lines. The spec database is a year of slow collection.</p>
<h2 id="heading-closing">Closing</h2>
<p>If you spot an error in any of the specifications above, please leave a comment — these are notoriously hard to keep current as governments occasionally update their requirements.</p>
]]></content:encoded></item></channel></rss>