<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
<title>Guides | My Geocode blog</title>
<subtitle>Step by step: geocode an address list, geolocate visitors, build an elevation profile, find the time zone of a point.</subtitle>
<link href="https://www.mygeocode.com/blog/category/guides/feed.xml" rel="self"/>
<link href="https://www.mygeocode.com/blog/category/guides/"/>
<id>https://www.mygeocode.com/blog/category/guides/</id>
<updated>2026-09-21T18:22:00+00:00</updated>
<author><name>My Geocode</name></author>
<entry><title>Monitor your key&#039;s usage before you hit a limit</title><link href="https://www.mygeocode.com/blog/monitor-your-key-s-usage-before-you-hit-a-limit/"/><id>https://www.mygeocode.com/blog/monitor-your-key-s-usage-before-you-hit-a-limit/</id><published>2026-09-21T18:22:00+00:00</published><updated>2026-09-21T18:22:00+00:00</updated><category term="guides" label="Guides"/><summary>Watching your quota headers as you go tells you when a limit is approaching, well before a request actually gets rejected.</summary><content type="html">&lt;p&gt;Finding out you are near your daily limit because a request just got rejected is the least useful way to find out. Every response already carries the numbers needed to see it coming.&lt;/p&gt;
&lt;h3 id=&quot;the-headers-to-watch&quot;&gt;The headers to watch&lt;/h3&gt;
&lt;div class=&quot;code&quot;&gt;&lt;pre&gt;&lt;code&gt;X-Quota-Limit: 2500
X-Quota-Used: 2210
X-Quota-Free-Remaining: 290
X-Quota-Network-Used: 2210
X-Credits-Remaining: 5.75
X-Key-IPs-Used: 2
X-Key-IPs-Limit: 3
X-Quota-Reset: 2026-09-22T00:00:00Z&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;X-Quota-Free-Remaining shrinking toward zero is your clearest early signal for the free daily allowance. X-Credits-Remaining tells you how much runway is left once you have moved past the free allowance and are drawing on prepaid credit at €0.0001 a request.&lt;/p&gt;
&lt;h3 id=&quot;setting-your-own-alert-threshold&quot;&gt;Setting your own alert threshold&lt;/h3&gt;
&lt;p&gt;Rather than waiting for a 429, log or alert once X-Quota-Free-Remaining drops below a threshold you choose, such as 10 percent of X-Quota-Limit, so there is time to react, whether that means slowing down a batch job, topping up credit, or simply expecting a quiet period until X-Quota-Reset.&lt;/p&gt;
&lt;h3 id=&quot;watching-key-ip-slots-too&quot;&gt;Watching key IP slots too&lt;/h3&gt;
&lt;p&gt;X-Key-IPs-Used and X-Key-IPs-Limit track how many different source addresses have used a given key under its rolling IP slot allowance. A key suddenly used from more addresses than expected, climbing toward its limit, is worth investigating on its own, separate from request volume, since it can point to a key being shared more widely than intended.&lt;/p&gt;
&lt;h3 id=&quot;building-a-simple-dashboard&quot;&gt;Building a simple dashboard&lt;/h3&gt;
&lt;p&gt;A small script that logs these headers on a sample of requests, or on every request if volume is low enough, gives you a usage trend over time without needing a separate monitoring call, since the numbers arrive on responses you were already making. Plotting X-Quota-Used against time across a day or a week shows usage patterns that a single snapshot never would.&lt;/p&gt;
&lt;h3 id=&quot;acting-before-you-hit-the-wall&quot;&gt;Acting before you hit the wall&lt;/h3&gt;
&lt;p&gt;Once you see a consistent pattern of running close to your limit, the two paths forward are the same two options available at any point: add prepaid credit for occasional overflow, or move to an Unlimited key at €50 a month if you are consistently past the free allowance. Either decision is easier to make calmly, ahead of time, than in response to a live 429 blocking real traffic.&lt;/p&gt;
&lt;p&gt;Reading quota headers as routine telemetry, not just as something to check when a request fails, turns a hard limit into a manageable trend. Current allowance and credit options are on the &lt;a href=&quot;/pricing/&quot;&gt;pricing page&lt;/a&gt;, and full header definitions are on the &lt;a href=&quot;/docs/rate-limits/&quot;&gt;rate limits docs&lt;/a&gt;.&lt;/p&gt;</content></entry>
<entry><title>Cross-check an address against its postal code</title><link href="https://www.mygeocode.com/blog/cross-check-an-address-against-its-postal-code/"/><id>https://www.mygeocode.com/blog/cross-check-an-address-against-its-postal-code/</id><published>2026-09-21T07:33:00+00:00</published><updated>2026-09-21T07:33:00+00:00</updated><category term="guides" label="Guides"/><summary>Catch a mismatched street address and postal code before an order ships, by resolving each independently and comparing what they actually reveal.</summary><content type="html">&lt;p&gt;A customer can type a real street address and a real postal code that do not actually belong together, most often by transposing digits or copying an old postal code into a new address. Neither field alone reveals the problem, since both are individually valid.&lt;/p&gt;
&lt;h3 id=&quot;resolving-each-field-independently&quot;&gt;Resolving each field independently&lt;/h3&gt;
&lt;p&gt;Geocode the full address with /v1/forward to see what postal code it actually resolves to, and separately look up the postal code the customer typed with /v1/postcode to see what area it covers.&lt;/p&gt;
&lt;div class=&quot;code&quot;&gt;&lt;pre&gt;&lt;code&gt;GET /v1/forward?q=10 Downing Street, London&amp;amp;limit=1&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div class=&quot;code&quot;&gt;&lt;pre&gt;&lt;code&gt;{
  &amp;quot;status&amp;quot;: &amp;quot;ok&amp;quot;,
  &amp;quot;results&amp;quot;: [
    {&amp;quot;formatted&amp;quot;: &amp;quot;10 Downing Street, London, UK&amp;quot;, &amp;quot;lat&amp;quot;: 51.5033, &amp;quot;lon&amp;quot;: -0.1276, &amp;quot;type&amp;quot;: &amp;quot;address&amp;quot;, &amp;quot;precision&amp;quot;: &amp;quot;house&amp;quot;, &amp;quot;confidence&amp;quot;: 0.96, &amp;quot;place_id&amp;quot;: &amp;quot;op678&amp;quot;, &amp;quot;components&amp;quot;: {&amp;quot;postcode&amp;quot;: &amp;quot;SW1A 2AA&amp;quot;, &amp;quot;city&amp;quot;: &amp;quot;London&amp;quot;}}
  ]
}&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div class=&quot;code&quot;&gt;&lt;pre&gt;&lt;code&gt;GET /v1/postcode?code=SW1A 1AA&amp;amp;country=GB&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div class=&quot;code&quot;&gt;&lt;pre&gt;&lt;code&gt;{
  &amp;quot;status&amp;quot;: &amp;quot;ok&amp;quot;,
  &amp;quot;postcode&amp;quot;: &amp;quot;SW1A 1AA&amp;quot;,
  &amp;quot;country_code&amp;quot;: &amp;quot;GB&amp;quot;,
  &amp;quot;results&amp;quot;: [{&amp;quot;lat&amp;quot;: 51.5014, &amp;quot;lon&amp;quot;: -0.1419, &amp;quot;components&amp;quot;: {&amp;quot;city&amp;quot;: &amp;quot;London&amp;quot;, &amp;quot;region&amp;quot;: &amp;quot;Greater London&amp;quot;}}]
}&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3 id=&quot;comparing-the-two-results&quot;&gt;Comparing the two results&lt;/h3&gt;
&lt;p&gt;If the postcode component from the address lookup does not match the postal code the customer typed, as in this example, that is a mismatch worth flagging, even though both the address and the postal code independently checked out as real. Comparing the two components directly is more reliable than comparing distance between the two sets of coordinates, since a postal code area can be small enough that even a genuine match will not sit exactly on the address&amp;#039;s coordinates.&lt;/p&gt;
&lt;h3 id=&quot;a-second-example-a-bigger-mismatch-worth-catching-earlier&quot;&gt;A second example: a bigger mismatch worth catching earlier&lt;/h3&gt;
&lt;p&gt;The same check also catches a far more obvious error, a postal code entered for an entirely different country or region than the street address itself, such as a UK-format code paired with an address that actually resolves inside another country. Comparing the country component from the address result against the country parameter used for the postcode lookup, before even reaching the more detailed postcode comparison, is a cheap first check that filters out this kind of larger mistake early.&lt;/p&gt;
&lt;h3 id=&quot;what-to-do-with-a-mismatch&quot;&gt;What to do with a mismatch&lt;/h3&gt;
&lt;p&gt;Show the customer both values side by side and ask them to confirm which one is correct, rather than silently picking one over the other or rejecting the order outright. A transposed digit is an easy fix once pointed out, and a genuine edge case where a postal code legitimately covers a boundary between two commonly used values is rare enough to handle with a quick manual confirmation.&lt;/p&gt;
&lt;h3 id=&quot;a-common-mistake-to-avoid&quot;&gt;A common mistake to avoid&lt;/h3&gt;
&lt;p&gt;Do not compare the two sets of coordinates directly with a distance threshold as a substitute for comparing the postcode components themselves. A postal code area can be geographically small enough that even a completely correct match will show some distance between the postcode&amp;#039;s representative point and the address&amp;#039;s specific building coordinates, and a small area code can also be geographically large enough that a genuinely mismatched pair still falls within a lenient distance threshold. Comparing the postcode text itself is a more direct and reliable check than trying to infer a match from distance.&lt;/p&gt;
&lt;h3 id=&quot;an-edge-case-worth-allowing-for&quot;&gt;An edge case worth allowing for&lt;/h3&gt;
&lt;p&gt;A postal code area that sits right on a boundary between two administrative regions can legitimately return a component set that looks slightly different depending on which endpoint resolved it, even when both are correct. Treat a close but not exact textual match, such as a difference only in how a region name is written, with a lighter touch than a fully different postal code entirely.&lt;/p&gt;
&lt;h3 id=&quot;checking-many-orders-at-once&quot;&gt;Checking many orders at once&lt;/h3&gt;
&lt;p&gt;For a batch of existing orders rather than a single new submission, both endpoints accept a bulk POST array, so a backlog of orders can be checked by sending the addresses as one bulk request to /v1/forward and the postal codes as another bulk request to /v1/postcode, then comparing the two result arrays by position.&lt;/p&gt;
&lt;h3 id=&quot;request-cost&quot;&gt;Request cost&lt;/h3&gt;
&lt;p&gt;This check uses two requests per order, one for the address and one for the postal code, both counted against your daily allowance the same as any other lookup. That is a reasonable cost for catching an error that would otherwise show up as a failed delivery.&lt;/p&gt;
&lt;p&gt;Catching a mismatched postal code before an order ships is worth the second request it takes to check. Details on both endpoints are in the &lt;a href=&quot;/docs/forward-geocoding/&quot;&gt;forward geocoding docs&lt;/a&gt; and the &lt;a href=&quot;/docs/postal-code-lookup/&quot;&gt;postal code lookup docs&lt;/a&gt;.&lt;/p&gt;</content></entry>
<entry><title>Add postal code auto-fill to a shipping form</title><link href="https://www.mygeocode.com/blog/add-postal-code-auto-fill-to-a-shipping-form/"/><id>https://www.mygeocode.com/blog/add-postal-code-auto-fill-to-a-shipping-form/</id><published>2026-09-20T21:49:00+00:00</published><updated>2026-09-20T21:49:00+00:00</updated><category term="guides" label="Guides"/><summary>Let a customer type just a postal code and have city and region fields fill in automatically, cutting a shipping form down by two fields.</summary><content type="html">&lt;p&gt;A shipping form with separate fields for postal code, city, and region asks a customer to type information that a single postal code often already implies.&lt;/p&gt;
&lt;h3 id=&quot;looking-up-the-code&quot;&gt;Looking up the code&lt;/h3&gt;
&lt;p&gt;Once a customer finishes typing a postal code and selects a country, or if country is already known from their account, send both to /v1/postcode.&lt;/p&gt;
&lt;div class=&quot;code&quot;&gt;&lt;pre&gt;&lt;code&gt;GET /v1/postcode?code=SW1A 1AA&amp;amp;country=GB&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div class=&quot;code&quot;&gt;&lt;pre&gt;&lt;code&gt;{
  &amp;quot;status&amp;quot;: &amp;quot;ok&amp;quot;,
  &amp;quot;postcode&amp;quot;: &amp;quot;SW1A 1AA&amp;quot;,
  &amp;quot;country_code&amp;quot;: &amp;quot;GB&amp;quot;,
  &amp;quot;results&amp;quot;: [
    {&amp;quot;lat&amp;quot;: 51.5014, &amp;quot;lon&amp;quot;: -0.1419, &amp;quot;components&amp;quot;: {&amp;quot;city&amp;quot;: &amp;quot;London&amp;quot;, &amp;quot;region&amp;quot;: &amp;quot;Greater London&amp;quot;, &amp;quot;country&amp;quot;: &amp;quot;GB&amp;quot;}}
  ]
}&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3 id=&quot;filling-the-form-fields&quot;&gt;Filling the form fields&lt;/h3&gt;
&lt;p&gt;Populate the city and region fields directly from the components object in the result, and let the customer confirm or correct them rather than locking the fields entirely, since a postal code occasionally spans a boundary or covers more than one commonly used place name.&lt;/p&gt;
&lt;h3 id=&quot;a-second-example-prefilling-a-billing-region-for-tax-purposes&quot;&gt;A second example: prefilling a billing region for tax purposes&lt;/h3&gt;
&lt;p&gt;The same lookup fits a signup or billing form that needs a customer&amp;#039;s region for tax calculation purposes, not just a shipping form. Triggering the same postcode lookup once a billing postal code is entered fills the region field consistently in both contexts using the identical request and response shape, so the same client-side and server-side handling code can serve both forms.&lt;/p&gt;
&lt;h3 id=&quot;triggering-the-lookup-at-the-right-moment&quot;&gt;Triggering the lookup at the right moment&lt;/h3&gt;
&lt;p&gt;Run the lookup once the postal code field loses focus, or once it reaches the expected length for the selected country, rather than on every keystroke, since a postal code is only meaningful to look up once it is complete. This keeps the feature to one request per completed field entry rather than one per character typed.&lt;/p&gt;
&lt;h3 id=&quot;a-common-mistake-to-avoid&quot;&gt;A common mistake to avoid&lt;/h3&gt;
&lt;p&gt;Do not hard-code a fixed expected length for every country&amp;#039;s postal code format. A five-digit assumption that works for one country will trigger the lookup too early or never at all for a country using a different length or an alphanumeric format like the example above. Either trigger on focus loss regardless of length, or maintain a small per-country length table if you want to trigger earlier for countries you support most often.&lt;/p&gt;
&lt;h3 id=&quot;handling-an-unrecognized-code&quot;&gt;Handling an unrecognized code&lt;/h3&gt;
&lt;p&gt;An empty results array means the code was not recognized for that country, most often because of a typo. Leave the city and region fields blank and let the customer fill them in manually rather than blocking form submission, since rejecting the field outright over an auto-fill failure is a worse experience than simply asking for manual entry in that one case.&lt;/p&gt;
&lt;h3 id=&quot;an-edge-case-a-code-covering-more-than-one-place-name&quot;&gt;An edge case: a code covering more than one place name&lt;/h3&gt;
&lt;p&gt;Some postal codes legitimately cover an area known locally by more than one place name, or sit right at a boundary between two commonly used city names. When this happens, showing the returned value as an editable suggestion rather than a fixed label lets the customer correct it to whichever name they actually use, without treating either as more or less official than the other.&lt;/p&gt;
&lt;h3 id=&quot;getting-exact-coordinates-once-the-address-is-complete&quot;&gt;Getting exact coordinates once the address is complete&lt;/h3&gt;
&lt;p&gt;Auto-fill from a postal code gives an area-level location, which is enough for city and region fields, but once the customer has typed a full street address on top of that, running the complete address through /v1/forward gets you the precise coordinates a shipping label or delivery routing system actually needs.&lt;/p&gt;
&lt;h3 id=&quot;what-it-costs&quot;&gt;What it costs&lt;/h3&gt;
&lt;p&gt;Each completed postal code field triggers one request. A shipping form processing many orders a day still uses this feature at a modest rate, one lookup per order rather than per keystroke, keeping it well within the 2,500 free requests a day included with every key for most stores.&lt;/p&gt;
&lt;p&gt;Auto-filling city and region from a postal code cuts a shipping form down by two fields for the large majority of customers who type a valid code. Full field definitions are on the &lt;a href=&quot;/docs/postal-code-lookup/&quot;&gt;postal code lookup docs&lt;/a&gt;.&lt;/p&gt;</content></entry>
<entry><title>Build a heatmap from raw IP address logs</title><link href="https://www.mygeocode.com/blog/build-a-heatmap-from-raw-ip-address-logs/"/><id>https://www.mygeocode.com/blog/build-a-heatmap-from-raw-ip-address-logs/</id><published>2026-09-20T09:25:00+00:00</published><updated>2026-09-20T09:25:00+00:00</updated><category term="guides" label="Guides"/><summary>Turn a plain access log full of IP addresses into a set of mapped coordinates ready to plot as a heatmap, by resolving unique addresses in bulk.</summary><content type="html">&lt;p&gt;An access log full of IP addresses has real geographic information buried in it, but only once each address has been resolved to coordinates that a mapping tool can actually plot.&lt;/p&gt;
&lt;h3 id=&quot;extracting-addresses-from-the-log&quot;&gt;Extracting addresses from the log&lt;/h3&gt;
&lt;p&gt;Pull the unique IP addresses out of your log file first, rather than looking up every single log line individually, since the same visitor address commonly appears many times across a session and there is no reason to pay for the same lookup repeatedly.&lt;/p&gt;
&lt;div class=&quot;code&quot;&gt;&lt;pre&gt;&lt;code&gt;sort logfile.txt | awk &amp;#039;{print $1}&amp;#039; | sort -u &amp;gt; unique_ips.txt&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3 id=&quot;resolving-in-bulk&quot;&gt;Resolving in bulk&lt;/h3&gt;
&lt;p&gt;Send the list of unique addresses as a bulk POST array to /v1/ip.&lt;/p&gt;
&lt;div class=&quot;code&quot;&gt;&lt;pre&gt;&lt;code&gt;POST /v1/ip
Content-Type: application/json

[&amp;quot;203.0.113.10&amp;quot;, &amp;quot;198.51.100.25&amp;quot;, &amp;quot;192.0.2.44&amp;quot;]&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div class=&quot;code&quot;&gt;&lt;pre&gt;&lt;code&gt;{
  &amp;quot;status&amp;quot;: &amp;quot;ok&amp;quot;,
  &amp;quot;results&amp;quot;: [
    {&amp;quot;ip&amp;quot;: &amp;quot;203.0.113.10&amp;quot;, &amp;quot;version&amp;quot;: 4, &amp;quot;found&amp;quot;: true, &amp;quot;country&amp;quot;: &amp;quot;Germany&amp;quot;, &amp;quot;country_code&amp;quot;: &amp;quot;DE&amp;quot;, &amp;quot;region&amp;quot;: &amp;quot;Berlin&amp;quot;, &amp;quot;city&amp;quot;: &amp;quot;Berlin&amp;quot;, &amp;quot;postcode&amp;quot;: &amp;quot;10115&amp;quot;, &amp;quot;lat&amp;quot;: 52.5300, &amp;quot;lon&amp;quot;: 13.3800, &amp;quot;timezone&amp;quot;: &amp;quot;Europe/Berlin&amp;quot;, &amp;quot;asn&amp;quot;: 1111, &amp;quot;org&amp;quot;: &amp;quot;Example ISP&amp;quot;},
    {&amp;quot;ip&amp;quot;: &amp;quot;198.51.100.25&amp;quot;, &amp;quot;version&amp;quot;: 4, &amp;quot;found&amp;quot;: true, &amp;quot;country&amp;quot;: &amp;quot;Spain&amp;quot;, &amp;quot;country_code&amp;quot;: &amp;quot;ES&amp;quot;, &amp;quot;region&amp;quot;: &amp;quot;Madrid&amp;quot;, &amp;quot;city&amp;quot;: &amp;quot;Madrid&amp;quot;, &amp;quot;postcode&amp;quot;: &amp;quot;28001&amp;quot;, &amp;quot;lat&amp;quot;: 40.4168, &amp;quot;lon&amp;quot;: -3.7038, &amp;quot;timezone&amp;quot;: &amp;quot;Europe/Madrid&amp;quot;, &amp;quot;asn&amp;quot;: 2222, &amp;quot;org&amp;quot;: &amp;quot;Example Networks&amp;quot;},
    {&amp;quot;ip&amp;quot;: &amp;quot;192.0.2.44&amp;quot;, &amp;quot;version&amp;quot;: 4, &amp;quot;found&amp;quot;: false}
  ]
}&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3 id=&quot;building-the-heatmap-dataset&quot;&gt;Building the heatmap dataset&lt;/h3&gt;
&lt;p&gt;Pair each returned lat and lon with the frequency count of that original address in your log, so a visitor who appears a hundred times in the log contributes proportionally more weight than one who appears once. Feed the resulting list of weighted coordinate points into whichever mapping or charting tool you use to render the heatmap.&lt;/p&gt;
&lt;h3 id=&quot;a-second-example-a-country-level-breakdown-instead&quot;&gt;A second example: a country-level breakdown instead&lt;/h3&gt;
&lt;p&gt;If a precise point-by-point heatmap is more detail than you need, aggregating by country_code instead of raw coordinates produces a simpler choropleth-style view, one figure per country rather than a scattered cloud of points. This uses exactly the same bulk lookup, just grouped differently once the results come back, so it costs nothing extra to build both views from the same resolved data.&lt;/p&gt;
&lt;h3 id=&quot;handling-addresses-that-do-not-resolve&quot;&gt;Handling addresses that do not resolve&lt;/h3&gt;
&lt;p&gt;An entry with found: false, as in the third result above, should simply be excluded from the heatmap rather than plotted at a default location, since including it would misleadingly cluster unresolved traffic somewhere on the map that has nothing to do with where it actually came from.&lt;/p&gt;
&lt;h3 id=&quot;a-common-mistake-to-avoid&quot;&gt;A common mistake to avoid&lt;/h3&gt;
&lt;p&gt;Do not skip filtering out obvious non-visitor traffic, such as internal health checks or monitoring services that hit your server constantly from a fixed address, before building the unique address list. A monitoring service polling every minute can rack up a disproportionate log line count that has nothing to do with real visitor geography, and while deduplication already limits it to one resolved point, that one point can still visually dominate a heatmap out of proportion to any real traffic pattern it represents.&lt;/p&gt;
&lt;h3 id=&quot;an-edge-case-hosting-and-data-center-traffic&quot;&gt;An edge case: hosting and data center traffic&lt;/h3&gt;
&lt;p&gt;The org field on a resolved address often reveals when traffic is coming from a data center or cloud hosting range rather than a residential or mobile connection, which is a reasonable signal for filtering out bot or scraper traffic before it skews a heatmap meant to represent real visitor locations.&lt;/p&gt;
&lt;h3 id=&quot;what-it-costs&quot;&gt;What it costs&lt;/h3&gt;
&lt;p&gt;Resolving unique addresses rather than every log line is what keeps this affordable, since request cost is one per unique address, not one per log line. A log with a million lines but only a few thousand unique visitor addresses costs a few thousand requests, not a million, comfortably manageable against prepaid credit or an Unlimited package for a large site, and often within the free allowance for a smaller one. Watching the X-Quota-Used header during a large batch job is a simple way to confirm usage is tracking as expected before the job finishes.&lt;/p&gt;
&lt;p&gt;Deduplicating before you resolve is the single biggest lever for keeping a log-based heatmap affordable. The &lt;a href=&quot;/docs/ipv4-lookup/&quot;&gt;IPv4 lookup docs&lt;/a&gt; cover the bulk request format in full.&lt;/p&gt;</content></entry>
<entry><title>Set sensible timeouts for high-volume batch jobs</title><link href="https://www.mygeocode.com/blog/set-sensible-timeouts-for-high-volume-batch-jobs/"/><id>https://www.mygeocode.com/blog/set-sensible-timeouts-for-high-volume-batch-jobs/</id><published>2026-09-19T21:44:00+00:00</published><updated>2026-09-19T21:44:00+00:00</updated><category term="guides" label="Guides"/><summary>A bulk request with thousands of items in one call needs a timeout budget that matches its size, not a default meant for a single lookup.</summary><content type="html">&lt;p&gt;A timeout value that works fine for a single address lookup will cut off a bulk request carrying a few thousand items well before the server has finished processing all of them, which looks like a failure even though the request would have completed given enough time.&lt;/p&gt;
&lt;h3 id=&quot;why-bulk-requests-need-more-time&quot;&gt;Why bulk requests need more time&lt;/h3&gt;
&lt;p&gt;Each item in a bulk array is a lookup in its own right, so a request with 2,000 items in it involves roughly 2,000 times the processing of a single lookup, even though it is one HTTP call from your side. A one or two second timeout, reasonable for a single address, is nowhere near enough for a request of that size.&lt;/p&gt;
&lt;h3 id=&quot;scaling-the-timeout-to-the-batch&quot;&gt;Scaling the timeout to the batch&lt;/h3&gt;
&lt;p&gt;Set your client&amp;#039;s timeout proportionally to the size of the array you are sending, with some margin for typical variation, rather than using a single fixed value across every request your code makes, small or large.&lt;/p&gt;
&lt;div class=&quot;code&quot;&gt;&lt;pre&gt;&lt;code&gt;timeout_seconds = max(5, item_count * 0.05)&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This is a starting point to tune against your own observed behavior, not a fixed number to treat as authoritative, since actual per-item processing time is not a published guarantee.&lt;/p&gt;
&lt;h3 id=&quot;preferring-smaller-chunks-over-one-giant-request&quot;&gt;Preferring smaller chunks over one giant request&lt;/h3&gt;
&lt;p&gt;Rather than pushing timeout values higher and higher to accommodate an ever larger single request, split a very large job into chunks of a few hundred to a few thousand items each. Smaller chunks need shorter, more predictable timeouts, and a failure partway through only costs you the current chunk rather than the entire job.&lt;/p&gt;
&lt;h3 id=&quot;handling-a-timeout-that-does-happen&quot;&gt;Handling a timeout that does happen&lt;/h3&gt;
&lt;p&gt;If a request does time out on your end, you may not know whether the server actually finished processing it or not. Rather than blindly resubmitting the same chunk, which could double-count against your quota if the original request did complete, check your quota headers from your most recent successful call to estimate whether the timed-out chunk likely went through, and resubmit cautiously.&lt;/p&gt;
&lt;h3 id=&quot;cost-is-unaffected-by-timeout-settings&quot;&gt;Cost is unaffected by timeout settings&lt;/h3&gt;
&lt;p&gt;A timeout is purely a client-side setting about how long you are willing to wait. It has no effect on what a request costs, still one request per item processed, whether your client waited the full duration or gave up early.&lt;/p&gt;
&lt;p&gt;Matching your timeout to your batch size, and preferring several smaller chunks over one very large request, keeps large jobs both reliable and easy to resume if something goes wrong. Chunking and quota strategy for large jobs is covered further on the &lt;a href=&quot;/docs/rate-limits/&quot;&gt;rate limits docs&lt;/a&gt;.&lt;/p&gt;</content></entry>
<entry><title>Add server-side geolocation to a Node or PHP app</title><link href="https://www.mygeocode.com/blog/add-server-side-geolocation-to-a-node-or-php-app/"/><id>https://www.mygeocode.com/blog/add-server-side-geolocation-to-a-node-or-php-app/</id><published>2026-09-19T09:07:00+00:00</published><updated>2026-09-19T09:07:00+00:00</updated><category term="guides" label="Guides"/><summary>Call the IP lookup endpoint directly from your backend code, whether you are running Node or PHP, with no client script involved.</summary><content type="html">&lt;p&gt;Geolocation belongs on the server for most use cases, personalization, defaults, and analytics among them, since it needs no cooperation from the visitor&amp;#039;s browser and no script running client-side at all.&lt;/p&gt;
&lt;h3 id=&quot;the-request-itself&quot;&gt;The request itself&lt;/h3&gt;
&lt;p&gt;Regardless of language, the call is the same plain HTTP GET request to /v1/ip, with the key sent as a header.&lt;/p&gt;
&lt;div class=&quot;code&quot;&gt;&lt;pre&gt;&lt;code&gt;GET /v1/ip?ip=203.0.113.60
X-API-Key: mg_live_examplekey123&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div class=&quot;code&quot;&gt;&lt;pre&gt;&lt;code&gt;{
  &amp;quot;status&amp;quot;: &amp;quot;ok&amp;quot;,
  &amp;quot;ip&amp;quot;: &amp;quot;203.0.113.60&amp;quot;,
  &amp;quot;version&amp;quot;: 4,
  &amp;quot;found&amp;quot;: true,
  &amp;quot;country&amp;quot;: &amp;quot;Netherlands&amp;quot;,
  &amp;quot;country_code&amp;quot;: &amp;quot;NL&amp;quot;,
  &amp;quot;region&amp;quot;: &amp;quot;North Holland&amp;quot;,
  &amp;quot;city&amp;quot;: &amp;quot;Amsterdam&amp;quot;,
  &amp;quot;postcode&amp;quot;: &amp;quot;1012&amp;quot;,
  &amp;quot;lat&amp;quot;: 52.3702,
  &amp;quot;lon&amp;quot;: 4.8952,
  &amp;quot;timezone&amp;quot;: &amp;quot;Europe/Amsterdam&amp;quot;,
  &amp;quot;asn&amp;quot;: 3344,
  &amp;quot;org&amp;quot;: &amp;quot;Example ISP&amp;quot;
}&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3 id=&quot;in-a-php-app&quot;&gt;In a PHP app&lt;/h3&gt;
&lt;p&gt;Fetch the visitor&amp;#039;s own address from the request, typically $_SERVER[&amp;#039;REMOTE_ADDR&amp;#039;], and pass it as the ip parameter, then make the HTTP request using whichever HTTP client your PHP setup already uses, curl or a library wrapper around it. Since this site itself is built as server-rendered PHP with no client-side JavaScript, this pattern fits naturally into a page&amp;#039;s normal rendering flow, with the location data available before the page markup is generated.&lt;/p&gt;
&lt;h3 id=&quot;in-a-node-app&quot;&gt;In a Node app&lt;/h3&gt;
&lt;p&gt;Read the visitor&amp;#039;s address from the incoming request object, commonly req.socket.remoteAddress or a header set by a reverse proxy such as X-Forwarded-For if one sits in front of your app, and make the same GET request using your HTTP client of choice before rendering a response.&lt;/p&gt;
&lt;h3 id=&quot;handling-a-proxy-in-front-of-your-app&quot;&gt;Handling a proxy in front of your app&lt;/h3&gt;
&lt;p&gt;If your app sits behind a load balancer or reverse proxy, the address your code sees directly might be the proxy&amp;#039;s own address rather than the visitor&amp;#039;s. Check whatever forwarded-address header your proxy sets and pass that address explicitly as the ip parameter, rather than relying on the connection&amp;#039;s raw remote address, which would otherwise geolocate your own infrastructure instead of the visitor.&lt;/p&gt;
&lt;h3 id=&quot;request-cost-either-way&quot;&gt;Request cost either way&lt;/h3&gt;
&lt;p&gt;The lookup itself costs the same one request whether it is called from PHP or Node, or any other backend language, since cost is tied to the request reaching the API, not to what made the call. Cache the result for the length of a session in either language to avoid repeating the lookup on every page.&lt;/p&gt;
&lt;p&gt;Server-side geolocation works the same way regardless of backend language, one HTTP call with the visitor&amp;#039;s address. Full authentication options are on the &lt;a href=&quot;/docs/authentication/&quot;&gt;authentication docs&lt;/a&gt;, and the field list is on the &lt;a href=&quot;/docs/ipv4-lookup/&quot;&gt;IPv4 lookup docs&lt;/a&gt;.&lt;/p&gt;</content></entry>
<entry><title>Localize date and time formats using the visitor&#039;s time zone</title><link href="https://www.mygeocode.com/blog/localize-date-and-time-formats-using-the-visitor-s-time-zone/"/><id>https://www.mygeocode.com/blog/localize-date-and-time-formats-using-the-visitor-s-time-zone/</id><published>2026-09-18T21:17:00+00:00</published><updated>2026-09-18T21:17:00+00:00</updated><category term="guides" label="Guides"/><summary>Show every date and time on a page in the visitor&#039;s own local time and format, rather than a fixed server time zone that reads correctly to almost nobody.</summary><content type="html">&lt;p&gt;A timestamp displayed in your server&amp;#039;s time zone reads correctly to almost nobody except whoever happens to sit in that same zone, which for a site with visitors anywhere is close to nobody at all.&lt;/p&gt;
&lt;h3 id=&quot;getting-the-visitor-s-zone&quot;&gt;Getting the visitor&amp;#039;s zone&lt;/h3&gt;
&lt;p&gt;An IP lookup returns a timezone field directly, giving you the identifier needed to localize any timestamp on the page.&lt;/p&gt;
&lt;div class=&quot;code&quot;&gt;&lt;pre&gt;&lt;code&gt;GET /v1/ip?ip=203.0.113.77&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div class=&quot;code&quot;&gt;&lt;pre&gt;&lt;code&gt;{
  &amp;quot;status&amp;quot;: &amp;quot;ok&amp;quot;,
  &amp;quot;ip&amp;quot;: &amp;quot;203.0.113.77&amp;quot;,
  &amp;quot;version&amp;quot;: 4,
  &amp;quot;found&amp;quot;: true,
  &amp;quot;country&amp;quot;: &amp;quot;Brazil&amp;quot;,
  &amp;quot;country_code&amp;quot;: &amp;quot;BR&amp;quot;,
  &amp;quot;region&amp;quot;: &amp;quot;Rio de Janeiro&amp;quot;,
  &amp;quot;city&amp;quot;: &amp;quot;Rio de Janeiro&amp;quot;,
  &amp;quot;postcode&amp;quot;: &amp;quot;20040&amp;quot;,
  &amp;quot;lat&amp;quot;: -22.9068,
  &amp;quot;lon&amp;quot;: -43.1729,
  &amp;quot;timezone&amp;quot;: &amp;quot;America/Sao_Paulo&amp;quot;,
  &amp;quot;asn&amp;quot;: 5566,
  &amp;quot;org&amp;quot;: &amp;quot;Example ISP&amp;quot;
}&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3 id=&quot;converting-stored-timestamps&quot;&gt;Converting stored timestamps&lt;/h3&gt;
&lt;p&gt;Store every timestamp in your database in UTC, as usual, and convert to the visitor&amp;#039;s local zone only at display time, on the server, using the timezone identifier you looked up. Since this site renders everything server-side with no client-side JavaScript, the conversion and formatting both happen before the page is sent, not afterward in the browser.&lt;/p&gt;
&lt;div class=&quot;code&quot;&gt;&lt;pre&gt;&lt;code&gt;local_time = convert_to_timezone(stored_utc_timestamp, &amp;quot;America/Sao_Paulo&amp;quot;)&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3 id=&quot;a-second-example-an-order-confirmation-timestamp&quot;&gt;A second example: an order confirmation timestamp&lt;/h3&gt;
&lt;p&gt;An order confirmation page showing &amp;quot;placed at&amp;quot; and &amp;quot;expected by&amp;quot; times is a good example of where this matters beyond a simple page header clock. Converting both timestamps through the same visitor zone, rather than leaving one in server time by oversight, keeps the two figures consistent and avoids a confusing situation where the expected delivery time appears to be earlier than the order time because one was converted and the other was not.&lt;/p&gt;
&lt;h3 id=&quot;formatting-not-just-converting&quot;&gt;Formatting, not just converting&lt;/h3&gt;
&lt;p&gt;Time zone and format are related but separate choices. A visitor in a zone that commonly uses a 24-hour clock and day-month-year date order benefits from formatting that matches, not just a shifted hour value written in a format that still looks foreign. Pair the country_code from the same IP lookup with a small formatting lookup table if you want to go beyond just adjusting the hour.&lt;/p&gt;
&lt;h3 id=&quot;a-common-mistake-to-avoid&quot;&gt;A common mistake to avoid&lt;/h3&gt;
&lt;p&gt;Do not implement the conversion as a fixed hour offset calculated once and applied to every timestamp going forward. A zone&amp;#039;s actual offset from UTC can change over the course of the year with daylight saving, so a timestamp converted correctly in one season can come out an hour wrong in another if the code applies a stored offset number rather than converting through the timezone identifier itself using a proper date and time library.&lt;/p&gt;
&lt;h3 id=&quot;an-edge-case-non-hour-offsets&quot;&gt;An edge case: non-hour offsets&lt;/h3&gt;
&lt;p&gt;Not every time zone sits at a whole-hour offset from UTC. Some are offset by 30 or 45 minutes rather than a full hour. Relying on a date library that understands the full IANA time zone identifier, rather than a simplified hour-only offset value, handles this correctly without any special-case code on your part. The utc_offset and abbreviation fields from the &lt;a href=&quot;/docs/timezone-lookup/&quot;&gt;timezone lookup docs&lt;/a&gt; are useful if you want to display the offset explicitly alongside a converted time.&lt;/p&gt;
&lt;h3 id=&quot;caching-the-zone-for-a-session&quot;&gt;Caching the zone for a session&lt;/h3&gt;
&lt;p&gt;Look up the time zone once per visitor session and reuse it for every timestamp rendered on every page during that visit, rather than calling the API again for each individual date shown, since the zone itself does not change mid-session.&lt;/p&gt;
&lt;h3 id=&quot;cost-of-localizing-a-whole-site&quot;&gt;Cost of localizing a whole site&lt;/h3&gt;
&lt;p&gt;One lookup per new session covers localization for every timestamp shown during that visit, one request regardless of how many dates appear on the page. This keeps even a content-heavy site well within the 2,500 free requests a day included with every key.&lt;/p&gt;
&lt;p&gt;Getting local time and date formatting right across a whole site comes down to one lookup per session and consistent server-side rendering after that. The &lt;a href=&quot;/docs/ipv4-lookup/&quot;&gt;IPv4 lookup docs&lt;/a&gt; and &lt;a href=&quot;/docs/timezone-lookup/&quot;&gt;timezone lookup docs&lt;/a&gt; cover both approaches to getting the zone.&lt;/p&gt;</content></entry>
<entry><title>Build a simple &quot;nearest office&quot; finder</title><link href="https://www.mygeocode.com/blog/build-a-simple-nearest-office-finder/"/><id>https://www.mygeocode.com/blog/build-a-simple-nearest-office-finder/</id><published>2026-09-18T08:58:00+00:00</published><updated>2026-09-18T08:58:00+00:00</updated><category term="guides" label="Guides"/><summary>Geocode a visitor&#039;s address once and compare it against a short fixed list of office coordinates in your own code to find the closest match.</summary><content type="html">&lt;p&gt;A company with a handful of offices does not need anything elaborate to answer &amp;quot;which office is closest to me,&amp;quot; just one geocoding request and a short comparison against a list that rarely changes.&lt;/p&gt;
&lt;h3 id=&quot;geocoding-the-input&quot;&gt;Geocoding the input&lt;/h3&gt;
&lt;p&gt;Whether the visitor types an address or a postal code, geocode it to get coordinates.&lt;/p&gt;
&lt;div class=&quot;code&quot;&gt;&lt;pre&gt;&lt;code&gt;GET /v1/forward?q=Berlin, Germany&amp;amp;limit=1&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div class=&quot;code&quot;&gt;&lt;pre&gt;&lt;code&gt;{
  &amp;quot;status&amp;quot;: &amp;quot;ok&amp;quot;,
  &amp;quot;results&amp;quot;: [
    {&amp;quot;formatted&amp;quot;: &amp;quot;Berlin, Germany&amp;quot;, &amp;quot;lat&amp;quot;: 52.5200, &amp;quot;lon&amp;quot;: 13.4050, &amp;quot;type&amp;quot;: &amp;quot;locality&amp;quot;, &amp;quot;precision&amp;quot;: &amp;quot;city&amp;quot;, &amp;quot;confidence&amp;quot;: 0.85, &amp;quot;place_id&amp;quot;: &amp;quot;bl456&amp;quot;, &amp;quot;components&amp;quot;: {&amp;quot;city&amp;quot;: &amp;quot;Berlin&amp;quot;, &amp;quot;country&amp;quot;: &amp;quot;DE&amp;quot;}}
  ]
}&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3 id=&quot;comparing-against-your-office-list&quot;&gt;Comparing against your office list&lt;/h3&gt;
&lt;p&gt;Keep your office coordinates as a small fixed list in your own code or configuration, since this rarely changes and does not need a lookup of its own each time.&lt;/p&gt;
&lt;div class=&quot;code&quot;&gt;&lt;pre&gt;&lt;code&gt;offices = [
  {&amp;quot;name&amp;quot;: &amp;quot;Berlin&amp;quot;, &amp;quot;lat&amp;quot;: 52.5170, &amp;quot;lon&amp;quot;: 13.3888},
  {&amp;quot;name&amp;quot;: &amp;quot;Paris&amp;quot;, &amp;quot;lat&amp;quot;: 48.8566, &amp;quot;lon&amp;quot;: 2.3522},
  {&amp;quot;name&amp;quot;: &amp;quot;London&amp;quot;, &amp;quot;lat&amp;quot;: 51.5074, &amp;quot;lon&amp;quot;: -0.1278}
]&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Calculate distance from the visitor&amp;#039;s coordinates to each office using a standard haversine formula, then sort by distance and return the closest match.&lt;/p&gt;
&lt;h3 id=&quot;a-second-example-adding-a-type-ahead-field&quot;&gt;A second example: adding a type-ahead field&lt;/h3&gt;
&lt;p&gt;Instead of a plain text box, backing the location field with /v1/autocomplete lets the visitor pick from suggested places as they type, reducing the chance of a typo producing an unexpected geocoding result. Once a suggestion is selected, resolving its place_id or its text through the same forward geocoding flow feeds straight into the same distance comparison already described.&lt;/p&gt;
&lt;h3 id=&quot;handling-a-city-level-match&quot;&gt;Handling a city-level match&lt;/h3&gt;
&lt;p&gt;Notice the precision in the example above is &amp;quot;city&amp;quot; rather than &amp;quot;house&amp;quot;, since the visitor only entered a city name. That is fine for an office finder, where the goal is picking the nearest office out of a handful of options, not pinpointing an exact building. A coarser precision here does not need special handling the way it might for a delivery address.&lt;/p&gt;
&lt;h3 id=&quot;a-common-mistake-to-avoid&quot;&gt;A common mistake to avoid&lt;/h3&gt;
&lt;p&gt;Do not forget to update the fixed office list in your own code when a physical office opens, closes, or moves. Because the list lives in your own configuration rather than in the API, it is easy for it to quietly go stale, silently pointing visitors toward a closed location or leaving a new one out of every comparison entirely. Treat that list as part of your regular content review, not a one-time setup step.&lt;/p&gt;
&lt;h3 id=&quot;an-edge-case-an-ambiguous-place-name&quot;&gt;An edge case: an ambiguous place name&lt;/h3&gt;
&lt;p&gt;A short place name can occasionally match more than one real location, such as a place name shared between a country and an unrelated region elsewhere. Passing the countries parameter to restrict candidate matches to the countries where you actually operate offices avoids this kind of ambiguity resolving to the wrong side of the world entirely.&lt;/p&gt;
&lt;h3 id=&quot;displaying-more-than-just-the-nearest-one&quot;&gt;Displaying more than just the nearest one&lt;/h3&gt;
&lt;p&gt;Showing the two or three closest offices, rather than only the single nearest one, gives a visitor near a regional boundary the chance to pick the one that actually suits them, such as one with a language or time zone that fits better even if it is a little farther by distance.&lt;/p&gt;
&lt;h3 id=&quot;what-it-costs&quot;&gt;What it costs&lt;/h3&gt;
&lt;p&gt;Each search is one geocoding request. The comparison against your office list happens entirely in your own code afterward and adds nothing further to your request count. A page like this, even with steady traffic, stays well inside the 2,500 free requests a day included with every key.&lt;/p&gt;
&lt;p&gt;A nearest office finder built this way needs exactly one request per visitor search, with the actual comparison logic living entirely on your side. Details on the request format are in the &lt;a href=&quot;/docs/forward-geocoding/&quot;&gt;forward geocoding docs&lt;/a&gt;.&lt;/p&gt;</content></entry>
<entry><title>Geocode addresses that include apartment or unit numbers</title><link href="https://www.mygeocode.com/blog/geocode-addresses-that-include-apartment-or-unit-numbers/"/><id>https://www.mygeocode.com/blog/geocode-addresses-that-include-apartment-or-unit-numbers/</id><published>2026-09-17T20:44:00+00:00</published><updated>2026-09-17T20:44:00+00:00</updated><category term="guides" label="Guides"/><summary>Unit and apartment numbers do not need to be stripped out before geocoding an address, and doing so can lose useful information for no benefit.</summary><content type="html">&lt;p&gt;An apartment number added onto a street address is a normal part of a lot of real addresses, and it is worth understanding how a geocoding lookup handles it rather than stripping it out before sending the request, which is a common but unnecessary habit.&lt;/p&gt;
&lt;h3 id=&quot;sending-the-full-address&quot;&gt;Sending the full address&lt;/h3&gt;
&lt;p&gt;Include the apartment or unit number as part of the free-text query, the same as any other part of the address.&lt;/p&gt;
&lt;div class=&quot;code&quot;&gt;&lt;pre&gt;&lt;code&gt;GET /v1/forward?q=Apt 4B, 350 Fifth Avenue, New York&amp;amp;limit=1&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div class=&quot;code&quot;&gt;&lt;pre&gt;&lt;code&gt;{
  &amp;quot;status&amp;quot;: &amp;quot;ok&amp;quot;,
  &amp;quot;query&amp;quot;: &amp;quot;Apt 4B, 350 Fifth Avenue, New York&amp;quot;,
  &amp;quot;results&amp;quot;: [
    {
      &amp;quot;formatted&amp;quot;: &amp;quot;350 Fifth Avenue, New York, NY&amp;quot;,
      &amp;quot;lat&amp;quot;: 40.7484,
      &amp;quot;lon&amp;quot;: -73.9857,
      &amp;quot;type&amp;quot;: &amp;quot;address&amp;quot;,
      &amp;quot;precision&amp;quot;: &amp;quot;house&amp;quot;,
      &amp;quot;confidence&amp;quot;: 0.95,
      &amp;quot;place_id&amp;quot;: &amp;quot;es234&amp;quot;,
      &amp;quot;components&amp;quot;: {&amp;quot;house_number&amp;quot;: &amp;quot;350&amp;quot;, &amp;quot;street&amp;quot;: &amp;quot;Fifth Avenue&amp;quot;, &amp;quot;city&amp;quot;: &amp;quot;New York&amp;quot;, &amp;quot;region&amp;quot;: &amp;quot;NY&amp;quot;, &amp;quot;country&amp;quot;: &amp;quot;US&amp;quot;}
    }
  ]
}&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3 id=&quot;why-the-unit-number-does-not-appear-in-coordinates&quot;&gt;Why the unit number does not appear in coordinates&lt;/h3&gt;
&lt;p&gt;Coordinates from a geocoding lookup identify a building, not a specific unit within it, since individual apartments do not have their own separate mapped location the way a building does. The apartment or unit portion of an address is not expected to change the lat and lon values, and its absence from the components object is expected too, not a sign that the input was ignored or mishandled.&lt;/p&gt;
&lt;h3 id=&quot;a-second-example-a-suite-number-in-a-commercial-building&quot;&gt;A second example: a suite number in a commercial building&lt;/h3&gt;
&lt;p&gt;The same pattern holds for a business address with a suite or floor number.&lt;/p&gt;
&lt;div class=&quot;code&quot;&gt;&lt;pre&gt;&lt;code&gt;GET /v1/forward?q=Suite 200, 1 Example Plaza, Chicago&amp;amp;limit=1&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;A large office building housing dozens of separate businesses still geocodes to one set of building coordinates regardless of which suite is named in the query, exactly as an apartment building does. Anything distinguishing one tenant from another within that building needs to live in your own records, not in the coordinates.&lt;/p&gt;
&lt;h3 id=&quot;a-common-mistake-to-avoid&quot;&gt;A common mistake to avoid&lt;/h3&gt;
&lt;p&gt;Do not try to work around the missing unit component by appending it onto the postcode or another component field yourself after the fact, such as writing a postal code as &amp;quot;20500 Apt 4B&amp;quot;. Doing this corrupts a field that other systems, including your own validation and any downstream address lookups, expect to contain only a real postal code. Keep the unit number in its own field entirely separate from every geocoded component.&lt;/p&gt;
&lt;h3 id=&quot;storing-the-unit-number-separately&quot;&gt;Storing the unit number separately&lt;/h3&gt;
&lt;p&gt;Rather than folding the unit number into the address string you geocode every time, store it as its own field in your own data model alongside the geocoded building address. This keeps the geocoding request focused on what actually affects the result, the building&amp;#039;s location, while still preserving the unit information for delivery labels or internal records.&lt;/p&gt;
&lt;h3 id=&quot;when-it-matters-for-delivery&quot;&gt;When it matters for delivery&lt;/h3&gt;
&lt;p&gt;A courier or postal service needs the unit number for actual delivery, even though it played no role in the coordinates. Keep both pieces, the geocoded building coordinates and the separately stored unit number, together in the final delivery record so nothing gets lost in the process.&lt;/p&gt;
&lt;h3 id=&quot;an-edge-case-worth-knowing&quot;&gt;An edge case worth knowing&lt;/h3&gt;
&lt;p&gt;A single building address can house a large number of separate residents or tenants, all sharing the same coordinates and the same postal code lookup result. If you are cross-checking a customer&amp;#039;s postal code against a forward geocoded address, as covered in the postal code cross-check guide, remember that this many-to-one relationship between units and a building is expected and is not itself a sign of a data problem.&lt;/p&gt;
&lt;h3 id=&quot;cost-is-unaffected&quot;&gt;Cost is unaffected&lt;/h3&gt;
&lt;p&gt;Whether or not an address includes an apartment or unit number, the lookup still costs one request, the same as any other forward geocoding call.&lt;/p&gt;
&lt;p&gt;Including a unit number in the query does no harm, and stripping it out is not necessary for accurate geocoding, though separating it into its own stored field afterward keeps your data cleaner. See the &lt;a href=&quot;/docs/forward-geocoding/&quot;&gt;forward geocoding docs&lt;/a&gt; for the full components list.&lt;/p&gt;</content></entry>
<entry><title>Add a distance-from-store calculator to a landing page</title><link href="https://www.mygeocode.com/blog/add-a-distance-from-store-calculator-to-a-landing-page/"/><id>https://www.mygeocode.com/blog/add-a-distance-from-store-calculator-to-a-landing-page/</id><published>2026-09-17T09:15:00+00:00</published><updated>2026-09-17T09:15:00+00:00</updated><category term="guides" label="Guides"/><summary>Let a visitor enter their address and see exactly how far your nearest location is, using one geocoding request and a distance formula in your own code.</summary><content type="html">&lt;p&gt;&amp;quot;How far is your nearest location from me&amp;quot; is a question a landing page can answer directly instead of sending a visitor off to a separate map to figure out themselves.&lt;/p&gt;
&lt;h3 id=&quot;geocoding-the-visitor-s-input&quot;&gt;Geocoding the visitor&amp;#039;s input&lt;/h3&gt;
&lt;p&gt;Take whatever address or postal code the visitor types into a simple form field and geocode it.&lt;/p&gt;
&lt;div class=&quot;code&quot;&gt;&lt;pre&gt;&lt;code&gt;GET /v1/forward?q=350 Fifth Avenue, New York&amp;amp;limit=1&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div class=&quot;code&quot;&gt;&lt;pre&gt;&lt;code&gt;{
  &amp;quot;status&amp;quot;: &amp;quot;ok&amp;quot;,
  &amp;quot;results&amp;quot;: [
    {&amp;quot;formatted&amp;quot;: &amp;quot;350 Fifth Avenue, New York, NY&amp;quot;, &amp;quot;lat&amp;quot;: 40.7484, &amp;quot;lon&amp;quot;: -73.9857, &amp;quot;type&amp;quot;: &amp;quot;address&amp;quot;, &amp;quot;precision&amp;quot;: &amp;quot;house&amp;quot;, &amp;quot;confidence&amp;quot;: 0.96, &amp;quot;place_id&amp;quot;: &amp;quot;es234&amp;quot;, &amp;quot;components&amp;quot;: {}}
  ]
}&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3 id=&quot;calculating-distance-to-each-store&quot;&gt;Calculating distance to each store&lt;/h3&gt;
&lt;p&gt;With the visitor&amp;#039;s coordinates in hand, calculate the straight-line distance from that point to each of your store locations, which you keep as a fixed list of coordinates in your own code, using a standard haversine formula.&lt;/p&gt;
&lt;div class=&quot;code&quot;&gt;&lt;pre&gt;&lt;code&gt;for store in stores:
    store.distance_km = haversine(store.lat, store.lon, 40.7484, -73.9857)&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3 id=&quot;showing-the-result&quot;&gt;Showing the result&lt;/h3&gt;
&lt;p&gt;Sort your store list by the calculated distance and show the closest one, or the closest few, along with the distance figure. Since this all runs server-side on form submission, there is no need for any client-side script, the page simply renders the result on the next page load.&lt;/p&gt;
&lt;h3 id=&quot;a-second-example-showing-the-three-nearest-instead-of-one&quot;&gt;A second example: showing the three nearest instead of one&lt;/h3&gt;
&lt;p&gt;Rather than reporting only the single nearest store, sorting the full list and showing the top three gives the visitor a genuine choice, particularly useful when the very nearest store might have different hours or stock than a slightly farther one the visitor would actually prefer. This costs nothing extra, since the sort happens against the same coordinates from the same single request.&lt;/p&gt;
&lt;h3 id=&quot;handling-a-low-confidence-match&quot;&gt;Handling a low-confidence match&lt;/h3&gt;
&lt;p&gt;If the visitor&amp;#039;s typed address comes back with a low confidence score, show the formatted result back to them for confirmation before running the distance calculation, rather than confidently reporting a distance based on a guess about what they meant.&lt;/p&gt;
&lt;h3 id=&quot;a-common-mistake-to-avoid&quot;&gt;A common mistake to avoid&lt;/h3&gt;
&lt;p&gt;Do not run the distance calculation against a query that resolved at a much coarser precision than the visitor probably intended, such as a bare city or region name matching a &amp;quot;locality&amp;quot; type result, without flagging that to the visitor first. A distance calculated from a city center rather than the visitor&amp;#039;s actual street can be off by several kilometers in either direction, enough to change which store looks nearest. Checking the precision field before reporting a confident-sounding distance figure avoids presenting a rough estimate as an exact one.&lt;/p&gt;
&lt;h3 id=&quot;letting-the-visitor-pick-among-ambiguous-matches&quot;&gt;Letting the visitor pick among ambiguous matches&lt;/h3&gt;
&lt;p&gt;Raising limit above 1 on the geocoding request returns several candidate matches when the typed input is genuinely ambiguous, such as a street name that exists in more than one town. Showing that short list and letting the visitor pick the right one before running the distance calculation is a better outcome than silently committing to whichever candidate happened to rank first.&lt;/p&gt;
&lt;h3 id=&quot;what-it-costs&quot;&gt;What it costs&lt;/h3&gt;
&lt;p&gt;Each calculation is one geocoding request, regardless of how many stores you compare the resulting coordinates against, since the store list itself does not require any further API calls once you have the visitor&amp;#039;s coordinates. A landing page with meaningful traffic still uses this feature at a rate of one request per visitor calculation, well inside the 2,500 free requests a day included with every key.&lt;/p&gt;
&lt;p&gt;A distance calculator like this answers a specific, common question directly on the page instead of pushing the visitor elsewhere to find out. Full request parameters are on the &lt;a href=&quot;/docs/forward-geocoding/&quot;&gt;forward geocoding docs&lt;/a&gt;.&lt;/p&gt;</content></entry>
<entry><title>Set up a fallback when a lookup returns no result</title><link href="https://www.mygeocode.com/blog/set-up-a-fallback-when-a-lookup-returns-no-result/"/><id>https://www.mygeocode.com/blog/set-up-a-fallback-when-a-lookup-returns-no-result/</id><published>2026-09-16T19:24:00+00:00</published><updated>2026-09-16T19:24:00+00:00</updated><category term="guides" label="Guides"/><summary>An empty result is a normal, valid response, not an error, and your code needs a deliberate path for handling it.</summary><content type="html">&lt;p&gt;A request that returns successfully with no useful result inside it is easy to mishandle, because it does not look like a failure at the HTTP level, it just does not have the answer you were hoping for.&lt;/p&gt;
&lt;h3 id=&quot;what-an-empty-result-looks-like&quot;&gt;What an empty result looks like&lt;/h3&gt;
&lt;p&gt;A forward geocoding search for something too vague or entirely fictional returns a 200 status with an empty results array, not an error code.&lt;/p&gt;
&lt;div class=&quot;code&quot;&gt;&lt;pre&gt;&lt;code&gt;GET /v1/forward?q=xyzzy nonexistent place&amp;amp;limit=1&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div class=&quot;code&quot;&gt;&lt;pre&gt;&lt;code&gt;{
  &amp;quot;status&amp;quot;: &amp;quot;ok&amp;quot;,
  &amp;quot;query&amp;quot;: &amp;quot;xyzzy nonexistent place&amp;quot;,
  &amp;quot;results&amp;quot;: []
}&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;An IP lookup for an address in an unallocated or private range returns found: false rather than an error, along with whatever fields it can still populate.&lt;/p&gt;
&lt;div class=&quot;code&quot;&gt;&lt;pre&gt;&lt;code&gt;{
  &amp;quot;status&amp;quot;: &amp;quot;ok&amp;quot;,
  &amp;quot;ip&amp;quot;: &amp;quot;10.0.0.5&amp;quot;,
  &amp;quot;version&amp;quot;: 4,
  &amp;quot;found&amp;quot;: false
}&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3 id=&quot;building-an-explicit-fallback-path&quot;&gt;Building an explicit fallback path&lt;/h3&gt;
&lt;p&gt;Check for an empty results array, or found: false, as its own branch in your code, separate from both the success path and the error handling path for 4xx and 5xx responses. Decide deliberately what happens next: ask the user to refine their input, fall back to a broader search with a higher limit, or show a clear &amp;quot;location not found&amp;quot; message rather than a blank space or a misleading default value.&lt;/p&gt;
&lt;h3 id=&quot;common-causes-worth-checking-for-first&quot;&gt;Common causes worth checking for first&lt;/h3&gt;
&lt;p&gt;A genuinely nonexistent place is one cause, but a poorly formatted input string, a typo, or a query that mixed languages or scripts unexpectedly can also produce an empty result for an address that actually exists. Before assuming the location itself does not exist, consider whether normalizing the input or trying a version with the countries parameter set would resolve it.&lt;/p&gt;
&lt;h3 id=&quot;logging-empty-results-separately&quot;&gt;Logging empty results separately&lt;/h3&gt;
&lt;p&gt;Track how often your integration hits an empty result path, separately from your error rate. A rising rate of empty results often points to a data quality problem upstream, in how addresses are being collected or formatted, rather than anything wrong with the lookup itself.&lt;/p&gt;
&lt;h3 id=&quot;cost-of-a-no-result-lookup&quot;&gt;Cost of a no-result lookup&lt;/h3&gt;
&lt;p&gt;An empty result still costs one request, the same as a successful match, since the lookup was performed either way. There is no separate discounted rate for a lookup that comes back empty.&lt;/p&gt;
&lt;p&gt;Treating an empty result as its own deliberate outcome, rather than an afterthought bolted onto error handling, makes an integration noticeably more solid. The &lt;a href=&quot;/docs/errors/&quot;&gt;errors page&lt;/a&gt; covers actual error responses, while empty results are documented alongside each endpoint&amp;#039;s normal response shape, such as the &lt;a href=&quot;/docs/forward-geocoding/&quot;&gt;forward geocoding docs&lt;/a&gt;.&lt;/p&gt;</content></entry>
<entry><title>Build a currency-aware pricing page from IP data</title><link href="https://www.mygeocode.com/blog/build-a-currency-aware-pricing-page-from-ip-data/"/><id>https://www.mygeocode.com/blog/build-a-currency-aware-pricing-page-from-ip-data/</id><published>2026-09-16T07:55:00+00:00</published><updated>2026-09-16T07:55:00+00:00</updated><category term="guides" label="Guides"/><summary>Show a visitor a price in a currency they recognize by detecting their country from IP, while keeping your own settlement currency fixed.</summary><content type="html">&lt;p&gt;A price shown in an unfamiliar currency makes a visitor stop and do mental math before they decide anything, which is exactly the moment a pricing page should be at its clearest.&lt;/p&gt;
&lt;h3 id=&quot;detecting-the-visitor-s-likely-currency&quot;&gt;Detecting the visitor&amp;#039;s likely currency&lt;/h3&gt;
&lt;p&gt;Look up the visitor&amp;#039;s IP address and read the country_code field, which you can map onto a currency using your own lookup table, the same table you would use if a customer had told you their country directly.&lt;/p&gt;
&lt;div class=&quot;code&quot;&gt;&lt;pre&gt;&lt;code&gt;GET /v1/ip?ip=203.0.113.150&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div class=&quot;code&quot;&gt;&lt;pre&gt;&lt;code&gt;{
  &amp;quot;status&amp;quot;: &amp;quot;ok&amp;quot;,
  &amp;quot;ip&amp;quot;: &amp;quot;203.0.113.150&amp;quot;,
  &amp;quot;version&amp;quot;: 4,
  &amp;quot;found&amp;quot;: true,
  &amp;quot;country&amp;quot;: &amp;quot;Canada&amp;quot;,
  &amp;quot;country_code&amp;quot;: &amp;quot;CA&amp;quot;,
  &amp;quot;region&amp;quot;: &amp;quot;Ontario&amp;quot;,
  &amp;quot;city&amp;quot;: &amp;quot;Toronto&amp;quot;,
  &amp;quot;postcode&amp;quot;: &amp;quot;M5H&amp;quot;,
  &amp;quot;lat&amp;quot;: 43.6511,
  &amp;quot;lon&amp;quot;: -79.3808,
  &amp;quot;timezone&amp;quot;: &amp;quot;America/Toronto&amp;quot;,
  &amp;quot;asn&amp;quot;: 4321,
  &amp;quot;org&amp;quot;: &amp;quot;Example ISP&amp;quot;
}&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3 id=&quot;display-currency-versus-settlement-currency&quot;&gt;Display currency versus settlement currency&lt;/h3&gt;
&lt;p&gt;Showing a converted price for readability does not require actually charging in that currency. My Geocode&amp;#039;s own pricing, for reference, is EUR only worldwide regardless of where a customer signs up from, and that same separation applies to your own pricing page: you can display a localized estimate while still settling every transaction in whatever currency your payment processor actually supports.&lt;/p&gt;
&lt;h3 id=&quot;labeling-converted-prices-honestly&quot;&gt;Labeling converted prices honestly&lt;/h3&gt;
&lt;p&gt;If you are showing a converted estimate rather than a price you will actually charge in that currency, label it clearly as an estimate, since exchange rates move and a visitor who expects to be charged exactly the number they saw will be caught off guard if the real charge lands in a different currency at a different rate.&lt;/p&gt;
&lt;h3 id=&quot;letting-visitors-override-the-detected-country&quot;&gt;Letting visitors override the detected country&lt;/h3&gt;
&lt;p&gt;Some visitors are traveling, using a VPN, or simply want to see pricing in a different currency than their detected one. A small currency selector next to the detected default costs nothing extra in requests and avoids trapping anyone in a wrong default.&lt;/p&gt;
&lt;h3 id=&quot;request-cost&quot;&gt;Request cost&lt;/h3&gt;
&lt;p&gt;One IP lookup per new visitor session, cached for the visit, is one request. Even a pricing page seeing a lot of daily traffic stays inside the 2,500 free requests a day included with every key, or the same allowance available from a single address without one.&lt;/p&gt;
&lt;p&gt;Detecting a likely currency from IP data removes a small but real point of friction on a pricing page, as long as the actual settlement currency is stated clearly. See the &lt;a href=&quot;/docs/ipv4-lookup/&quot;&gt;IPv4 lookup docs&lt;/a&gt; for the field list, and My Geocode&amp;#039;s own &lt;a href=&quot;/pricing/&quot;&gt;pricing page&lt;/a&gt; for how we handle this ourselves.&lt;/p&gt;</content></entry>
<entry><title>Read a confidence score correctly before you trust it</title><link href="https://www.mygeocode.com/blog/read-a-confidence-score-correctly-before-you-trust-it/"/><id>https://www.mygeocode.com/blog/read-a-confidence-score-correctly-before-you-trust-it/</id><published>2026-09-15T20:16:00+00:00</published><updated>2026-09-15T20:16:00+00:00</updated><category term="guides" label="Guides"/><summary>A confidence score tells you how sure the match is, not whether the address exists, and the two are easy to conflate.</summary><content type="html">&lt;p&gt;A confidence value between 0.0 and 1.0 on a geocoding result looks like a simple pass or fail signal, but treating it that way misses what it is actually measuring.&lt;/p&gt;
&lt;h3 id=&quot;what-confidence-measures&quot;&gt;What confidence measures&lt;/h3&gt;
&lt;p&gt;Confidence reflects how sure the match is, given the input text, that this particular result is the one the input was referring to. A high confidence score on an ambiguous or oddly formatted input still means the system is confident in its best guess, not that the input was definitely correct or that the location definitely exists as described.&lt;/p&gt;
&lt;div class=&quot;code&quot;&gt;&lt;pre&gt;&lt;code&gt;GET /v1/forward?q=Main Street, Springfield&amp;amp;limit=1&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div class=&quot;code&quot;&gt;&lt;pre&gt;&lt;code&gt;{
  &amp;quot;status&amp;quot;: &amp;quot;ok&amp;quot;,
  &amp;quot;query&amp;quot;: &amp;quot;Main Street, Springfield&amp;quot;,
  &amp;quot;results&amp;quot;: [
    {&amp;quot;formatted&amp;quot;: &amp;quot;Main Street, Springfield, IL&amp;quot;, &amp;quot;lat&amp;quot;: 39.7817, &amp;quot;lon&amp;quot;: -89.6501, &amp;quot;type&amp;quot;: &amp;quot;address&amp;quot;, &amp;quot;precision&amp;quot;: &amp;quot;street&amp;quot;, &amp;quot;confidence&amp;quot;: 0.62, &amp;quot;place_id&amp;quot;: &amp;quot;sp901&amp;quot;, &amp;quot;components&amp;quot;: {}}
  ]
}&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;A confidence of 0.62 here reflects the fact that many towns share the name Springfield, so even a correct match to one specific Springfield carries some inherent uncertainty about whether it is the Springfield the input meant.&lt;/p&gt;
&lt;h3 id=&quot;confidence-versus-precision&quot;&gt;Confidence versus precision&lt;/h3&gt;
&lt;p&gt;These are two different fields measuring two different things. Precision describes how specific the match is, house, street, or a coarser level. Confidence describes how sure the system is about the match it made. A &amp;quot;house&amp;quot; precision result can still carry a moderate confidence score if the input was ambiguous about which house on which street, and a &amp;quot;street&amp;quot; precision result can carry a high confidence score if the match, though coarse, was unambiguous.&lt;/p&gt;
&lt;h3 id=&quot;setting-a-threshold&quot;&gt;Setting a threshold&lt;/h3&gt;
&lt;p&gt;Pick a confidence threshold based on what a wrong result would cost you. A threshold of 0.8 or higher makes sense for something automated and consequential, like auto-filling a shipping label with no human review. A lower threshold is reasonable for something with a human checking the result afterward, like a suggestion shown to a customer for confirmation.&lt;/p&gt;
&lt;h3 id=&quot;combining-with-the-countries-parameter&quot;&gt;Combining with the countries parameter&lt;/h3&gt;
&lt;p&gt;Ambiguous matches like the Springfield example above are often resolved by narrowing the search with the countries parameter when you already know which country the address should be in, which raises the odds of a high-confidence match on the first try rather than needing a second lookup.&lt;/p&gt;
&lt;h3 id=&quot;cost-stays-the-same&quot;&gt;Cost stays the same&lt;/h3&gt;
&lt;p&gt;Reading confidence correctly does not change what a lookup costs, still one request either way. What it changes is whether you act on the result with the right amount of trust.&lt;/p&gt;
&lt;p&gt;Confidence is a specific, narrow signal, not a general correctness guarantee, and reading it that way avoids acting too quickly on a shaky match. Full field definitions are on the &lt;a href=&quot;/docs/forward-geocoding/&quot;&gt;forward geocoding docs&lt;/a&gt;.&lt;/p&gt;</content></entry>
<entry><title>Test your integration without spending your daily quota</title><link href="https://www.mygeocode.com/blog/test-your-integration-without-spending-your-daily-quota/"/><id>https://www.mygeocode.com/blog/test-your-integration-without-spending-your-daily-quota/</id><published>2026-09-15T08:23:00+00:00</published><updated>2026-09-15T08:23:00+00:00</updated><category term="guides" label="Guides"/><summary>The free daily allowance exists for exactly this, building and testing an integration before any money or a key is involved.</summary><content type="html">&lt;p&gt;Building an integration usually means dozens or hundreds of test calls before anything goes near production traffic, and none of that testing needs to touch a paid tier.&lt;/p&gt;
&lt;h3 id=&quot;testing-with-no-key-at-all&quot;&gt;Testing with no key at all&lt;/h3&gt;
&lt;p&gt;2,500 free requests a day are available from any address with no key needed at all, which is enough for most integration work: wiring up request formatting, checking response parsing, and working through error handling for a handful of test cases each.&lt;/p&gt;
&lt;div class=&quot;code&quot;&gt;&lt;pre&gt;&lt;code&gt;GET /v1/forward?q=Test Address, Test City&amp;amp;limit=1&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;No X-API-Key header, no Authorization header, nothing but the request itself.&lt;/p&gt;
&lt;h3 id=&quot;watching-quota-headers-while-testing&quot;&gt;Watching quota headers while testing&lt;/h3&gt;
&lt;p&gt;Even without a key, every response carries the same quota headers, so you can watch X-Quota-Used and X-Quota-Free-Remaining climb and fall exactly as they would in production, which is useful for testing how your own code handles those headers before it matters for real.&lt;/p&gt;
&lt;div class=&quot;code&quot;&gt;&lt;pre&gt;&lt;code&gt;X-Quota-Limit: 2500
X-Quota-Used: 42
X-Quota-Free-Remaining: 2458&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3 id=&quot;testing-a-key-without-spending-credit&quot;&gt;Testing a key without spending credit&lt;/h3&gt;
&lt;p&gt;Once you sign up and generate a key, that key also gets its own 2,500 free requests a day, separate testing room that does not touch prepaid credit or count against an Unlimited package. This is the point to test key-specific behavior, such as the different ways to send the key, header, bearer token, basic auth, or query parameter, all without spending anything.&lt;/p&gt;
&lt;h3 id=&quot;deliberately-testing-error-paths&quot;&gt;Deliberately testing error paths&lt;/h3&gt;
&lt;p&gt;Send a request with an invalid parameter to see a 400, or an intentionally wrong key to see a 401, so your error handling code has actually been exercised before launch rather than assumed to work. Each of these test calls, successful or not, is one request against the free allowance, not something that should worry you at normal testing volumes.&lt;/p&gt;
&lt;h3 id=&quot;moving-to-production-traffic&quot;&gt;Moving to production traffic&lt;/h3&gt;
&lt;p&gt;Once real traffic starts, the same free allowance keeps covering typical daily usage for a lot of smaller integrations, with prepaid credit or an Unlimited package only needed once volume passes 2,500 requests a day.&lt;/p&gt;
&lt;p&gt;There is no separate sandbox environment needed here, since the free allowance already gives a full testing ground with the real API. Get started by generating a key from the &lt;a href=&quot;/dashboard/keys&quot;&gt;dashboard&lt;/a&gt;, or read the &lt;a href=&quot;/docs/authentication/&quot;&gt;authentication docs&lt;/a&gt; first if you want to see the key formats before signing up.&lt;/p&gt;</content></entry>
<entry><title>Combine postcode and country for faster lookups</title><link href="https://www.mygeocode.com/blog/combine-postcode-and-country-for-faster-lookups/"/><id>https://www.mygeocode.com/blog/combine-postcode-and-country-for-faster-lookups/</id><published>2026-09-14T22:03:00+00:00</published><updated>2026-09-14T22:03:00+00:00</updated><category term="guides" label="Guides"/><summary>When you already have a clean postal code and country, skip free-text parsing entirely and go straight to the postcode endpoint for a more direct match.</summary><content type="html">&lt;p&gt;Forward geocoding is built to handle messy free text, which means it does work behind the scenes to figure out what kind of input it is looking at. If you already know you have a clean postal code and a country, there is a more direct path.&lt;/p&gt;
&lt;h3 id=&quot;going-straight-to-the-postcode-endpoint&quot;&gt;Going straight to the postcode endpoint&lt;/h3&gt;
&lt;p&gt;Rather than assembling a free-text string and sending it to /v1/forward, send the code and country directly to /v1/postcode.&lt;/p&gt;
&lt;div class=&quot;code&quot;&gt;&lt;pre&gt;&lt;code&gt;GET /v1/postcode?code=90210&amp;amp;country=US&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div class=&quot;code&quot;&gt;&lt;pre&gt;&lt;code&gt;{
  &amp;quot;status&amp;quot;: &amp;quot;ok&amp;quot;,
  &amp;quot;postcode&amp;quot;: &amp;quot;90210&amp;quot;,
  &amp;quot;country_code&amp;quot;: &amp;quot;US&amp;quot;,
  &amp;quot;results&amp;quot;: [
    {&amp;quot;lat&amp;quot;: 34.0901, &amp;quot;lon&amp;quot;: -118.4065, &amp;quot;components&amp;quot;: {&amp;quot;city&amp;quot;: &amp;quot;Beverly Hills&amp;quot;, &amp;quot;region&amp;quot;: &amp;quot;CA&amp;quot;, &amp;quot;country&amp;quot;: &amp;quot;US&amp;quot;}}
  ]
}&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3 id=&quot;why-this-is-the-better-fit&quot;&gt;Why this is the better fit&lt;/h3&gt;
&lt;p&gt;A postal code and country pair is already fully structured input, so there is no ambiguity for the endpoint to resolve the way there can be with a free-text string that might match multiple similarly worded places. Using the endpoint built for exactly this input shape gets you a cleaner match with less risk of an unexpected result.&lt;/p&gt;
&lt;h3 id=&quot;a-second-example-an-alphanumeric-postal-code&quot;&gt;A second example: an alphanumeric postal code&lt;/h3&gt;
&lt;p&gt;The same call works the same way for a country using letters and numbers together rather than digits alone.&lt;/p&gt;
&lt;div class=&quot;code&quot;&gt;&lt;pre&gt;&lt;code&gt;GET /v1/postcode?code=K1A 0B1&amp;amp;country=CA&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Passing the country parameter is what makes this reliable, since a code string alone is not always unique across countries, and the country parameter is what tells the endpoint which postal system to interpret it against.&lt;/p&gt;
&lt;h3 id=&quot;when-you-still-need-forward-geocoding&quot;&gt;When you still need forward geocoding&lt;/h3&gt;
&lt;p&gt;If you need a specific street-level match rather than the general area a postal code covers, forward geocoding with the full address is still the right tool, since a postal code lookup resolves to the area the code covers, not a specific building within it. Use /v1/postcode for area-level lookups such as validating a shipping zone, and /v1/forward when you need house-level precision.&lt;/p&gt;
&lt;h3 id=&quot;a-common-mistake-to-avoid&quot;&gt;A common mistake to avoid&lt;/h3&gt;
&lt;p&gt;Do not assume a postal code always maps to exactly one point that represents every address inside it precisely. The coordinates returned by /v1/postcode represent the code&amp;#039;s covered area, which can be a single city block or, in some countries, a much larger zone. Treating that returned point as if it were the exact location of a specific customer&amp;#039;s building, rather than a representative point for the whole code, will introduce error into anything distance-sensitive built on top of it.&lt;/p&gt;
&lt;h3 id=&quot;combining-both-in-a-workflow&quot;&gt;Combining both in a workflow&lt;/h3&gt;
&lt;p&gt;A common pattern is validating the postal code and country pair first with /v1/postcode, then, once that passes, sending the full street address to /v1/forward to get exact coordinates. That is two requests per submission, one for each stage, rather than trying to force one endpoint to do both jobs.&lt;/p&gt;
&lt;h3 id=&quot;an-edge-case-a-code-that-returns-no-results&quot;&gt;An edge case: a code that returns no results&lt;/h3&gt;
&lt;p&gt;An empty results array means the code was not recognized for the country given, which is a different situation from an error response. Treat it the way you would any other unrecognized input, by asking the customer to re-check what they entered, rather than surfacing it as a system error.&lt;/p&gt;
&lt;h3 id=&quot;what-it-costs&quot;&gt;What it costs&lt;/h3&gt;
&lt;p&gt;Each postcode lookup is one request, the same cost as a forward geocoding lookup. Choosing the right endpoint for structured input does not change what it costs, it changes how directly you get to a correct result.&lt;/p&gt;
&lt;p&gt;Reaching for the postcode endpoint when you already have clean, structured input avoids the extra ambiguity that comes with reconstructing a free-text string just to parse it back apart again. Full details are on the &lt;a href=&quot;/docs/postal-code-lookup/&quot;&gt;postal code lookup docs&lt;/a&gt;.&lt;/p&gt;</content></entry>
<entry><title>Build a simple IP reputation check for signups</title><link href="https://www.mygeocode.com/blog/build-a-simple-ip-reputation-check-for-signups/"/><id>https://www.mygeocode.com/blog/build-a-simple-ip-reputation-check-for-signups/</id><published>2026-09-14T09:06:00+00:00</published><updated>2026-09-14T09:06:00+00:00</updated><category term="guides" label="Guides"/><summary>Compare an IP address&#039;s location and network details against what a signup form claims, catching obvious mismatches without a dedicated fraud tool.</summary><content type="html">&lt;p&gt;A dedicated fraud detection service is more than most small signup forms need. A simple check comparing what an IP address suggests against what a signup form claims catches a reasonable share of obviously mismatched attempts on its own.&lt;/p&gt;
&lt;h3 id=&quot;getting-ip-details-at-signup&quot;&gt;Getting IP details at signup&lt;/h3&gt;
&lt;div class=&quot;code&quot;&gt;&lt;pre&gt;&lt;code&gt;GET /v1/ip?ip=198.51.100.200&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div class=&quot;code&quot;&gt;&lt;pre&gt;&lt;code&gt;{
  &amp;quot;status&amp;quot;: &amp;quot;ok&amp;quot;,
  &amp;quot;ip&amp;quot;: &amp;quot;198.51.100.200&amp;quot;,
  &amp;quot;version&amp;quot;: 4,
  &amp;quot;found&amp;quot;: true,
  &amp;quot;country&amp;quot;: &amp;quot;Brazil&amp;quot;,
  &amp;quot;country_code&amp;quot;: &amp;quot;BR&amp;quot;,
  &amp;quot;region&amp;quot;: &amp;quot;Sao Paulo&amp;quot;,
  &amp;quot;city&amp;quot;: &amp;quot;Sao Paulo&amp;quot;,
  &amp;quot;postcode&amp;quot;: &amp;quot;01310&amp;quot;,
  &amp;quot;lat&amp;quot;: -23.5505,
  &amp;quot;lon&amp;quot;: -46.6333,
  &amp;quot;timezone&amp;quot;: &amp;quot;America/Sao_Paulo&amp;quot;,
  &amp;quot;asn&amp;quot;: 8901,
  &amp;quot;org&amp;quot;: &amp;quot;Example Cloud Provider&amp;quot;
}&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3 id=&quot;comparing-country-against-form-input&quot;&gt;Comparing country against form input&lt;/h3&gt;
&lt;p&gt;If the signup form asks for a billing country and the value entered does not match country_code from the IP lookup, that alone is not proof of anything wrong, since travel and VPN use are both common and legitimate. Treat a mismatch as one point added to a simple score rather than an automatic rejection.&lt;/p&gt;
&lt;h3 id=&quot;weighing-network-type&quot;&gt;Weighing network type&lt;/h3&gt;
&lt;p&gt;The org field often reveals whether an address belongs to a residential ISP or a data center or cloud hosting provider. A signup coming from a cloud hosting network, where a real individual customer would rarely be browsing from, is worth a higher score than one from a recognizable residential ISP, though again not an automatic rejection on its own.&lt;/p&gt;
&lt;h3 id=&quot;a-second-example-a-small-known-asn-list&quot;&gt;A second example: a small known-ASN list&lt;/h3&gt;
&lt;p&gt;If you have noticed particular ASN values showing up repeatedly around abusive signups, keeping a short list of those specific asn numbers and adding a fixed score whenever a new signup matches one is a lightweight way to encode that pattern going forward, without needing a full third-party ASN reputation database for a check this modest in scope.&lt;/p&gt;
&lt;div class=&quot;code&quot;&gt;&lt;pre&gt;&lt;code&gt;if ip_info[&amp;quot;asn&amp;quot;] in known_abuse_asns:
    score += 2&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3 id=&quot;combining-signals-into-a-score&quot;&gt;Combining signals into a score&lt;/h3&gt;
&lt;p&gt;Add up a small number of simple checks: country mismatch, hosting network origin, and anything else specific to your own signup flow, into a single numeric score. Route anything above a threshold you set to manual review or an extra verification step, such as email confirmation, rather than blocking outright.&lt;/p&gt;
&lt;h3 id=&quot;a-common-mistake-to-avoid&quot;&gt;A common mistake to avoid&lt;/h3&gt;
&lt;p&gt;Do not score every VPN or hosting-range signup as automatically high risk without any other context. Plenty of legitimate customers browse from a company network that happens to route through a data center IP range, or from a VPN they use for entirely ordinary privacy reasons. Weighting network type as one signal among several, rather than a single disqualifying flag, keeps the check from turning away real customers over a false positive.&lt;/p&gt;
&lt;h3 id=&quot;an-edge-case-many-signups-from-one-shared-address&quot;&gt;An edge case: many signups from one shared address&lt;/h3&gt;
&lt;p&gt;A shared office network or a large mobile carrier&amp;#039;s carrier-grade NAT can put a lot of unrelated, legitimate signups behind the same IP address in a short window. If you are also tracking signup volume per IP as part of your check, set that threshold generously enough to allow for this, or watch it alongside the country and network signals rather than as a rule on its own.&lt;/p&gt;
&lt;h3 id=&quot;keeping-it-proportionate&quot;&gt;Keeping it proportionate&lt;/h3&gt;
&lt;p&gt;This kind of check is meant to catch obviously low-effort abuse, not to replace a real fraud prevention system for a business where fraud losses are a serious concern. Treat it as a first filter, not a final verdict.&lt;/p&gt;
&lt;h3 id=&quot;request-cost&quot;&gt;Request cost&lt;/h3&gt;
&lt;p&gt;One lookup per signup attempt is one request. Even a form receiving a steady stream of signups stays well inside the 2,500 free requests a day included with every key for typical volumes. The X-Quota-Used and X-Quota-Free-Remaining response headers are a simple way to keep an eye on how much of that allowance a sudden spike in signup attempts is consuming.&lt;/p&gt;
&lt;p&gt;A lightweight score built from a couple of IP-derived signals catches more than doing nothing, without the overhead of a dedicated fraud platform. The &lt;a href=&quot;/docs/ipv4-lookup/&quot;&gt;IPv4 lookup docs&lt;/a&gt; list every field available for this kind of check.&lt;/p&gt;</content></entry>
<entry><title>Add elevation data to a GPX route file</title><link href="https://www.mygeocode.com/blog/add-elevation-data-to-a-gpx-route-file/"/><id>https://www.mygeocode.com/blog/add-elevation-data-to-a-gpx-route-file/</id><published>2026-09-13T20:09:00+00:00</published><updated>2026-09-13T20:09:00+00:00</updated><category term="guides" label="Guides"/><summary>Fill in missing or inaccurate elevation values in a recorded GPX track by looking up each point&#039;s real elevation directly, in bulk.</summary><content type="html">&lt;p&gt;A GPX file recorded from a phone or a basic GPS device often has elevation values that are missing entirely or noticeably inaccurate, since consumer GPS altitude readings are usually far less reliable than the latitude and longitude they record.&lt;/p&gt;
&lt;h3 id=&quot;extracting-the-points&quot;&gt;Extracting the points&lt;/h3&gt;
&lt;p&gt;Parse the GPX file&amp;#039;s track points, pulling out just the latitude and longitude for each one, and discard the existing elevation values you plan to replace.&lt;/p&gt;
&lt;div class=&quot;code&quot;&gt;&lt;pre&gt;&lt;code&gt;&amp;lt;trkpt lat=&amp;quot;45.8326&amp;quot; lon=&amp;quot;6.8652&amp;quot;&amp;gt;&amp;lt;ele&amp;gt;1050&amp;lt;/ele&amp;gt;&amp;lt;/trkpt&amp;gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3 id=&quot;looking-up-correct-elevation&quot;&gt;Looking up correct elevation&lt;/h3&gt;
&lt;p&gt;Send the extracted coordinates to /v1/elevation, either through the points parameter for a smaller file or a bulk POST array for a larger one.&lt;/p&gt;
&lt;div class=&quot;code&quot;&gt;&lt;pre&gt;&lt;code&gt;POST /v1/elevation
Content-Type: application/json

[{&amp;quot;lat&amp;quot;: 45.8326, &amp;quot;lon&amp;quot;: 6.8652}, {&amp;quot;lat&amp;quot;: 45.8400, &amp;quot;lon&amp;quot;: 6.8700}]&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div class=&quot;code&quot;&gt;&lt;pre&gt;&lt;code&gt;{
  &amp;quot;status&amp;quot;: &amp;quot;ok&amp;quot;,
  &amp;quot;results&amp;quot;: [
    {&amp;quot;lat&amp;quot;: 45.8326, &amp;quot;lon&amp;quot;: 6.8652, &amp;quot;elevation_m&amp;quot;: 1035},
    {&amp;quot;lat&amp;quot;: 45.8400, &amp;quot;lon&amp;quot;: 6.8700, &amp;quot;elevation_m&amp;quot;: 1210}
  ]
}&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3 id=&quot;a-second-example-checking-a-single-trailhead&quot;&gt;A second example: checking a single trailhead&lt;/h3&gt;
&lt;p&gt;Not every use needs a full track processed. Before publishing a hike description, a single call confirms the elevation at the trailhead itself.&lt;/p&gt;
&lt;div class=&quot;code&quot;&gt;&lt;pre&gt;&lt;code&gt;GET /v1/elevation?lat=45.8326&amp;amp;lon=6.8652&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This returns the same result shape with one entry, useful for spot-checking a starting or ending elevation figure quoted in trail notes without needing to touch the full GPX file.&lt;/p&gt;
&lt;h3 id=&quot;writing-the-values-back&quot;&gt;Writing the values back&lt;/h3&gt;
&lt;p&gt;Results come back in the same order as the coordinates you sent, so write elevation_m back into each track point&amp;#039;s ele tag by matching position, replacing the original recorded value with the looked-up one.&lt;/p&gt;
&lt;div class=&quot;code&quot;&gt;&lt;pre&gt;&lt;code&gt;&amp;lt;trkpt lat=&amp;quot;45.8326&amp;quot; lon=&amp;quot;6.8652&amp;quot;&amp;gt;&amp;lt;ele&amp;gt;1035&amp;lt;/ele&amp;gt;&amp;lt;/trkpt&amp;gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3 id=&quot;a-common-mistake-to-avoid&quot;&gt;A common mistake to avoid&lt;/h3&gt;
&lt;p&gt;Do not mix the points query parameter format and a bulk POST body across a single job without being consistent about how you match results back to track points. The points parameter and the JSON array both return results in the order given, but pulling coordinates for one point via a query parameter and the rest via a bulk POST, then merging the two response sets by assumption rather than by explicit coordinate matching, is an easy way to introduce an off-by-one error into the written-back file.&lt;/p&gt;
&lt;h3 id=&quot;deciding-how-many-points-to-enrich&quot;&gt;Deciding how many points to enrich&lt;/h3&gt;
&lt;p&gt;A GPX file can have thousands of track points recorded at short intervals. Enriching every single one is one request per point, which adds up on a long recorded track. Downsampling to a coarser interval before the elevation lookup, then interpolating between the looked-up values for points in between, is a reasonable way to cut down request count while still producing an accurate looking profile.&lt;/p&gt;
&lt;h3 id=&quot;an-edge-case-coastal-and-low-lying-points&quot;&gt;An edge case: coastal and low-lying points&lt;/h3&gt;
&lt;p&gt;A track that dips along a coastline or through a low-lying delta can legitimately return an elevation_m value at or near zero, or occasionally a small negative number for land below sea level. Neither is an error. Treat a value near zero as a plausible real reading for that terrain rather than filtering it out as a bad data point.&lt;/p&gt;
&lt;h3 id=&quot;what-it-costs&quot;&gt;What it costs&lt;/h3&gt;
&lt;p&gt;Each enriched point is one request. A modest hiking track with a few hundred points, sampled down from a much larger raw recording, stays well within the 2,500 free requests a day included with every key.&lt;/p&gt;
&lt;p&gt;Cleaning up elevation data this way turns a shaky phone-recorded track into something worth sharing or analyzing. The &lt;a href=&quot;/docs/elevation-lookup/&quot;&gt;elevation lookup docs&lt;/a&gt; describe both the points parameter and the bulk request format.&lt;/p&gt;</content></entry>
<entry><title>Show the right business hours based on visitor location</title><link href="https://www.mygeocode.com/blog/show-the-right-business-hours-based-on-visitor-location/"/><id>https://www.mygeocode.com/blog/show-the-right-business-hours-based-on-visitor-location/</id><published>2026-09-13T08:34:00+00:00</published><updated>2026-09-13T08:34:00+00:00</updated><category term="guides" label="Guides"/><summary>Display &quot;open now&quot; or &quot;closed&quot; correctly for a global audience by resolving the visitor&#039;s local time zone before comparing it against your posted hours.</summary><content type="html">&lt;p&gt;&amp;quot;We&amp;#039;re open until 6pm&amp;quot; is only useful if the visitor reading it knows which 6pm you mean. A visitor in a different time zone from your business needs that comparison done in their own local time, not yours.&lt;/p&gt;
&lt;h3 id=&quot;getting-the-visitor-s-time-zone&quot;&gt;Getting the visitor&amp;#039;s time zone&lt;/h3&gt;
&lt;p&gt;A single IP lookup returns a timezone field directly, giving you what you need without a separate call.&lt;/p&gt;
&lt;div class=&quot;code&quot;&gt;&lt;pre&gt;&lt;code&gt;GET /v1/ip?ip=203.0.113.44&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div class=&quot;code&quot;&gt;&lt;pre&gt;&lt;code&gt;{
  &amp;quot;status&amp;quot;: &amp;quot;ok&amp;quot;,
  &amp;quot;ip&amp;quot;: &amp;quot;203.0.113.44&amp;quot;,
  &amp;quot;version&amp;quot;: 4,
  &amp;quot;found&amp;quot;: true,
  &amp;quot;country&amp;quot;: &amp;quot;Japan&amp;quot;,
  &amp;quot;country_code&amp;quot;: &amp;quot;JP&amp;quot;,
  &amp;quot;region&amp;quot;: &amp;quot;Tokyo&amp;quot;,
  &amp;quot;city&amp;quot;: &amp;quot;Tokyo&amp;quot;,
  &amp;quot;postcode&amp;quot;: &amp;quot;100-0001&amp;quot;,
  &amp;quot;lat&amp;quot;: 35.6762,
  &amp;quot;lon&amp;quot;: 139.6503,
  &amp;quot;timezone&amp;quot;: &amp;quot;Asia/Tokyo&amp;quot;,
  &amp;quot;asn&amp;quot;: 2345,
  &amp;quot;org&amp;quot;: &amp;quot;Example Telecom&amp;quot;
}&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3 id=&quot;comparing-against-your-business-hours&quot;&gt;Comparing against your business hours&lt;/h3&gt;
&lt;p&gt;Convert your own business hours, stored against your business&amp;#039;s own time zone, into the visitor&amp;#039;s zone using the timezone identifier you just retrieved, then compare against the visitor&amp;#039;s current local time to decide whether to show &amp;quot;open now&amp;quot; or &amp;quot;closed&amp;quot; along with the local time you are showing it as of.&lt;/p&gt;
&lt;h3 id=&quot;a-second-example-a-business-with-more-than-one-location&quot;&gt;A second example: a business with more than one location&lt;/h3&gt;
&lt;p&gt;A business with locations in different cities should look up each location&amp;#039;s own posted hours against its own stored zone, then compare each separately against the visitor&amp;#039;s zone, rather than assuming one set of hours applies everywhere. This matters most when a visitor is comparing two locations on the same page, since one might read as open and the other closed at the exact same moment if they sit in different zones or observe daylight saving differently.&lt;/p&gt;
&lt;h3 id=&quot;being-explicit-about-which-time-zone-you-mean&quot;&gt;Being explicit about which time zone you mean&lt;/h3&gt;
&lt;p&gt;Rather than silently converting and hoping the visitor understands, show both pieces clearly, something like &amp;quot;Currently closed. Opens at 9am your time (Asia/Tokyo).&amp;quot; Being explicit avoids confusion when a business operates across a boundary that shifts with daylight saving on one side but not the other.&lt;/p&gt;
&lt;h3 id=&quot;a-common-mistake-to-avoid&quot;&gt;A common mistake to avoid&lt;/h3&gt;
&lt;p&gt;Do not compute the open or closed comparison once and cache that boolean result for the rest of the visitor&amp;#039;s session. A comparison made at 5:55pm will read &amp;quot;open&amp;quot; and stay wrong by 6:05pm if the underlying open state is cached rather than recalculated. Cache the visitor&amp;#039;s time zone identifier, since that is genuinely stable for a session, but recompute the actual open or closed comparison fresh on every render.&lt;/p&gt;
&lt;h3 id=&quot;an-edge-case-zones-that-do-not-observe-daylight-saving&quot;&gt;An edge case: zones that do not observe daylight saving&lt;/h3&gt;
&lt;p&gt;Some regions keep a fixed offset year-round while a neighboring region shifts twice a year, which means the gap between two zones is not constant across the calendar. If your comparison logic hard-codes an offset in hours rather than working from the timezone identifier and letting your date library handle the conversion, it will drift out of sync for exactly the weeks around a daylight saving change.&lt;/p&gt;
&lt;h3 id=&quot;caching-the-zone-not-the-open-state&quot;&gt;Caching the zone, not the open state&lt;/h3&gt;
&lt;p&gt;The visitor&amp;#039;s time zone identifier is stable for a session and worth caching. Whether the business is currently open changes throughout the day, so recompute that comparison at render time using the cached zone rather than caching the open or closed state itself.&lt;/p&gt;
&lt;h3 id=&quot;checking-a-specific-past-or-future-moment&quot;&gt;Checking a specific past or future moment&lt;/h3&gt;
&lt;p&gt;If you need to know what the offset was, or will be, at a specific moment rather than right now, such as confirming what time an order placed yesterday actually was in the customer&amp;#039;s zone, /v1/timezone accepts an optional time parameter as a unix timestamp for exactly that kind of historical or future check.&lt;/p&gt;
&lt;h3 id=&quot;request-cost&quot;&gt;Request cost&lt;/h3&gt;
&lt;p&gt;One IP lookup per new visitor session covers this feature. That is a single request cached for the length of the visit, keeping a busy storefront site well within the 2,500 free requests a day included with every key.&lt;/p&gt;
&lt;p&gt;Getting &amp;quot;open now&amp;quot; right for a global audience is a matter of one lookup and a straightforward time comparison, not a complicated feature. The &lt;a href=&quot;/docs/ipv4-lookup/&quot;&gt;IPv4 lookup docs&lt;/a&gt; list the full response shape.&lt;/p&gt;</content></entry>
<entry><title>Handle addresses with no house number</title><link href="https://www.mygeocode.com/blog/handle-addresses-with-no-house-number/"/><id>https://www.mygeocode.com/blog/handle-addresses-with-no-house-number/</id><published>2026-09-12T19:02:00+00:00</published><updated>2026-09-12T19:02:00+00:00</updated><category term="guides" label="Guides"/><summary>A rural address, a landmark, or a new development without an assigned house number still geocodes correctly, just at a coarser precision than a full street address.</summary><content type="html">&lt;p&gt;Not every address has a house number. Rural routes, some new developments, and well-known landmarks are often referred to without one, and a geocoding request built around expecting a house number every time will misread what a valid result for these looks like.&lt;/p&gt;
&lt;h3 id=&quot;what-the-response-looks-like-without-one&quot;&gt;What the response looks like without one&lt;/h3&gt;
&lt;div class=&quot;code&quot;&gt;&lt;pre&gt;&lt;code&gt;GET /v1/forward?q=Golden Gate Bridge, San Francisco&amp;amp;limit=1&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div class=&quot;code&quot;&gt;&lt;pre&gt;&lt;code&gt;{
  &amp;quot;status&amp;quot;: &amp;quot;ok&amp;quot;,
  &amp;quot;query&amp;quot;: &amp;quot;Golden Gate Bridge, San Francisco&amp;quot;,
  &amp;quot;results&amp;quot;: [
    {
      &amp;quot;formatted&amp;quot;: &amp;quot;Golden Gate Bridge, San Francisco, CA&amp;quot;,
      &amp;quot;lat&amp;quot;: 37.8199,
      &amp;quot;lon&amp;quot;: -122.4783,
      &amp;quot;type&amp;quot;: &amp;quot;address&amp;quot;,
      &amp;quot;precision&amp;quot;: &amp;quot;street&amp;quot;,
      &amp;quot;confidence&amp;quot;: 0.9,
      &amp;quot;place_id&amp;quot;: &amp;quot;gb567&amp;quot;,
      &amp;quot;components&amp;quot;: {&amp;quot;city&amp;quot;: &amp;quot;San Francisco&amp;quot;, &amp;quot;region&amp;quot;: &amp;quot;CA&amp;quot;, &amp;quot;country&amp;quot;: &amp;quot;US&amp;quot;}
    }
  ]
}&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Notice there is no house_number field in components here, and precision reads &amp;quot;street&amp;quot; rather than &amp;quot;house&amp;quot;. Both of these are expected for an address that genuinely does not have a house number, not signs of a failed or partial lookup.&lt;/p&gt;
&lt;h3 id=&quot;reading-precision-correctly&quot;&gt;Reading precision correctly&lt;/h3&gt;
&lt;p&gt;Treat precision as a description of how specific the match is, not as an error indicator. A &amp;quot;house&amp;quot; precision means the match resolved to a specific building. A &amp;quot;street&amp;quot; precision means it resolved to a street or a point along it, without pinning down an exact building, which is exactly right for a landmark or an address genuinely lacking a house number.&lt;/p&gt;
&lt;h3 id=&quot;a-second-example-a-well-known-public-space&quot;&gt;A second example: a well-known public space&lt;/h3&gt;
&lt;div class=&quot;code&quot;&gt;&lt;pre&gt;&lt;code&gt;GET /v1/forward?q=Central Park, New York&amp;amp;limit=1&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;A named public space like this typically resolves with type set to something broader than &amp;quot;address&amp;quot; and a precision that reflects an area rather than a single point, along with a components object that may include only a city and region. This is the same pattern as the bridge example: a real, useful result described honestly as covering an area rather than a specific building, because that is what the query actually referred to.&lt;/p&gt;
&lt;h3 id=&quot;adjusting-validation-logic-accordingly&quot;&gt;Adjusting validation logic accordingly&lt;/h3&gt;
&lt;p&gt;If your form validation currently requires a house_number field to be present before accepting an address as complete, that check will incorrectly reject legitimate rural addresses and landmarks. Base acceptance criteria on confidence and precision matching what you actually need, rather than requiring every specific component to be populated.&lt;/p&gt;
&lt;h3 id=&quot;a-common-mistake-to-avoid&quot;&gt;A common mistake to avoid&lt;/h3&gt;
&lt;p&gt;Do not treat an empty components object, or one missing several fields, as equivalent to a failed request. A failed request comes back with an error status and an error code, described on the &lt;a href=&quot;/docs/errors/&quot;&gt;errors docs&lt;/a&gt;. A successful result with sparse components is a different, entirely normal outcome, and conflating the two in your error handling will cause valid addresses to be logged and treated as failures.&lt;/p&gt;
&lt;h3 id=&quot;letting-customers-confirm-instead-of-reject&quot;&gt;Letting customers confirm instead of reject&lt;/h3&gt;
&lt;p&gt;For an address without a house number, showing the formatted result back to the customer for confirmation works better than rejecting the submission outright. This keeps a real address usable while still catching genuinely bad input elsewhere.&lt;/p&gt;
&lt;h3 id=&quot;the-reverse-direction-has-the-same-pattern&quot;&gt;The reverse direction has the same pattern&lt;/h3&gt;
&lt;p&gt;Reverse geocoding a coordinate that sits in open countryside or water can likewise come back with a coarser precision and fewer populated components than a coordinate sitting on a specific building&amp;#039;s footprint. The &lt;a href=&quot;/docs/reverse-geocoding/&quot;&gt;reverse geocoding docs&lt;/a&gt; describe the same precision values from that direction.&lt;/p&gt;
&lt;h3 id=&quot;cost-stays-the-same&quot;&gt;Cost stays the same&lt;/h3&gt;
&lt;p&gt;An address without a house number costs the same one request as any other forward geocoding lookup. Nothing about missing components changes how the request is billed against your daily allowance or credit balance.&lt;/p&gt;
&lt;p&gt;Handling this correctly is mostly about reading precision and confidence as intended, rather than assuming every valid address must fully populate every component. The &lt;a href=&quot;/docs/forward-geocoding/&quot;&gt;forward geocoding docs&lt;/a&gt; explain each field in detail.&lt;/p&gt;</content></entry>
<entry><title>Geocode addresses in bulk from a CRM export</title><link href="https://www.mygeocode.com/blog/geocode-addresses-in-bulk-from-a-crm-export/"/><id>https://www.mygeocode.com/blog/geocode-addresses-in-bulk-from-a-crm-export/</id><published>2026-09-12T07:44:00+00:00</published><updated>2026-09-12T07:44:00+00:00</updated><category term="guides" label="Guides"/><summary>Export a customer list from your CRM and resolve every address to coordinates in one bulk request, instead of calling the API once per record.</summary><content type="html">&lt;p&gt;A CRM export is usually a flat file of customer records with an address field and nothing else location-related, no coordinates, no verified components. Turning that into something you can map or segment regionally means geocoding the whole export.&lt;/p&gt;
&lt;h3 id=&quot;preparing-the-export&quot;&gt;Preparing the export&lt;/h3&gt;
&lt;p&gt;Pull the address column out of your CRM export as a plain array, keeping the customer ID aligned with each address by position, since you will need to match results back to records afterward.&lt;/p&gt;
&lt;div class=&quot;code&quot;&gt;&lt;pre&gt;&lt;code&gt;POST /v1/forward
Content-Type: application/json

[&amp;quot;1600 Pennsylvania Avenue, Washington&amp;quot;, &amp;quot;221B Baker Street, London&amp;quot;]&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div class=&quot;code&quot;&gt;&lt;pre&gt;&lt;code&gt;{
  &amp;quot;status&amp;quot;: &amp;quot;ok&amp;quot;,
  &amp;quot;results&amp;quot;: [
    {&amp;quot;formatted&amp;quot;: &amp;quot;1600 Pennsylvania Avenue NW, Washington, DC 20500&amp;quot;, &amp;quot;lat&amp;quot;: 38.8977, &amp;quot;lon&amp;quot;: -77.0365, &amp;quot;type&amp;quot;: &amp;quot;address&amp;quot;, &amp;quot;precision&amp;quot;: &amp;quot;house&amp;quot;, &amp;quot;confidence&amp;quot;: 0.97, &amp;quot;place_id&amp;quot;: &amp;quot;def456&amp;quot;, &amp;quot;components&amp;quot;: {}},
    {&amp;quot;formatted&amp;quot;: &amp;quot;221B Baker Street, London, UK&amp;quot;, &amp;quot;lat&amp;quot;: 51.5237, &amp;quot;lon&amp;quot;: -0.1585, &amp;quot;type&amp;quot;: &amp;quot;address&amp;quot;, &amp;quot;precision&amp;quot;: &amp;quot;house&amp;quot;, &amp;quot;confidence&amp;quot;: 0.95, &amp;quot;place_id&amp;quot;: &amp;quot;abc123&amp;quot;, &amp;quot;components&amp;quot;: {}}
  ]
}&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3 id=&quot;writing-results-back-to-the-crm&quot;&gt;Writing results back to the CRM&lt;/h3&gt;
&lt;p&gt;Match each result to its customer by the position you tracked before sending the request, then write the coordinates and components back into your CRM either through its own API or a bulk import, depending on what your CRM supports. Store the confidence and precision fields too, so anything with a weak match can be flagged for cleanup rather than treated as verified.&lt;/p&gt;
&lt;h3 id=&quot;a-second-example-an-international-customer-base&quot;&gt;A second example: an international customer base&lt;/h3&gt;
&lt;p&gt;A CRM with customers across several countries benefits from passing the countries parameter alongside each region&amp;#039;s batch, restricting candidate matches to the expected country and cutting down on the rare case where a street name common in more than one country resolves to the wrong one. Splitting the export into per-country batches before sending each as its own bulk request is a reasonable way to apply this without changing anything else about the workflow.&lt;/p&gt;
&lt;h3 id=&quot;a-common-mistake-to-avoid&quot;&gt;A common mistake to avoid&lt;/h3&gt;
&lt;p&gt;Do not deduplicate or reorder the address array before sending it without keeping a separate mapping back to the original customer IDs. Results return in the same order as the array you sent, and once that order is decoupled from your customer records without a saved index, there is no reliable way to match a coordinate pair back to the right customer afterward. Keep the position-to-ID mapping in memory or in a temporary column for the duration of the job.&lt;/p&gt;
&lt;h3 id=&quot;re-running-on-new-records-only&quot;&gt;Re-running on new records only&lt;/h3&gt;
&lt;p&gt;Once the initial export is geocoded, there is no need to reprocess the whole customer base again later. Track which records already have coordinates and only send new or address-updated records through the endpoint on subsequent runs, keeping ongoing request usage proportional to new activity rather than your total customer count.&lt;/p&gt;
&lt;h3 id=&quot;handling-records-that-resolve-poorly&quot;&gt;Handling records that resolve poorly&lt;/h3&gt;
&lt;p&gt;A record with a low confidence score or missing several expected components is worth flagging in a review queue rather than writing silently alongside your fully verified records. This keeps a bad address from quietly contaminating a regional report or a map view built from the backfilled data.&lt;/p&gt;
&lt;h3 id=&quot;what-it-costs&quot;&gt;What it costs&lt;/h3&gt;
&lt;p&gt;A one-time backfill of an existing CRM costs one request per customer record with an address, run as a single bulk call or a handful of chunks. A CRM with a few thousand customers might use more than the 2,500 free requests a day in one sitting, which is a reasonable moment to either spread the job across a couple of days or move onto prepaid credit for a one-time push.&lt;/p&gt;
&lt;p&gt;Once the backfill is done, ongoing geocoding for new customers is small and steady rather than a recurring bulk job. See the &lt;a href=&quot;/docs/forward-geocoding/&quot;&gt;forward geocoding docs&lt;/a&gt; for the full request and response shape.&lt;/p&gt;</content></entry>
<entry><title>Build a time zone picker that defaults to the visitor&#039;s zone</title><link href="https://www.mygeocode.com/blog/build-a-time-zone-picker-that-defaults-to-the-visitor-s-zone/"/><id>https://www.mygeocode.com/blog/build-a-time-zone-picker-that-defaults-to-the-visitor-s-zone/</id><published>2026-09-11T19:15:00+00:00</published><updated>2026-09-11T19:15:00+00:00</updated><category term="guides" label="Guides"/><summary>Pre-select a visitor&#039;s own time zone in a settings form using their IP address, instead of asking them to search for it themselves.</summary><content type="html">&lt;p&gt;Scrolling through a list of time zone names to find your own is a small annoyance that a sensible default removes for almost everyone.&lt;/p&gt;
&lt;h3 id=&quot;detecting-the-zone&quot;&gt;Detecting the zone&lt;/h3&gt;
&lt;p&gt;The /v1/ip endpoint returns a timezone field directly, so a single call gives you both the location and the time zone identifier together, without a separate call to the timezone endpoint.&lt;/p&gt;
&lt;div class=&quot;code&quot;&gt;&lt;pre&gt;&lt;code&gt;GET /v1/ip?ip=203.0.113.88&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div class=&quot;code&quot;&gt;&lt;pre&gt;&lt;code&gt;{
  &amp;quot;status&amp;quot;: &amp;quot;ok&amp;quot;,
  &amp;quot;ip&amp;quot;: &amp;quot;203.0.113.88&amp;quot;,
  &amp;quot;version&amp;quot;: 4,
  &amp;quot;found&amp;quot;: true,
  &amp;quot;country&amp;quot;: &amp;quot;Australia&amp;quot;,
  &amp;quot;country_code&amp;quot;: &amp;quot;AU&amp;quot;,
  &amp;quot;region&amp;quot;: &amp;quot;New South Wales&amp;quot;,
  &amp;quot;city&amp;quot;: &amp;quot;Sydney&amp;quot;,
  &amp;quot;postcode&amp;quot;: &amp;quot;2000&amp;quot;,
  &amp;quot;lat&amp;quot;: -33.8688,
  &amp;quot;lon&amp;quot;: 151.2093,
  &amp;quot;timezone&amp;quot;: &amp;quot;Australia/Sydney&amp;quot;,
  &amp;quot;asn&amp;quot;: 7890,
  &amp;quot;org&amp;quot;: &amp;quot;Example Networks&amp;quot;
}&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3 id=&quot;pre-selecting-the-picker&quot;&gt;Pre-selecting the picker&lt;/h3&gt;
&lt;p&gt;Set the timezone value from this response as the selected option in your settings form when it first renders, server-side, before the page reaches the visitor. This works the same whether the picker is a dropdown of standard zone identifiers or a searchable list, since the value you are pre-selecting is just a standard identifier string like the one in the example.&lt;/p&gt;
&lt;h3 id=&quot;a-second-example-multiple-people-on-one-booking&quot;&gt;A second example: multiple people on one booking&lt;/h3&gt;
&lt;p&gt;A travel or event booking form that collects details for more than one traveler benefits from detecting a zone once for the person filling out the form, then applying it as the shared default for every traveler entry rather than looking it up again per traveler. Each traveler field stays editable individually, since a group booking often includes people joining from a different zone than whoever is filling in the form.&lt;/p&gt;
&lt;h3 id=&quot;letting-the-visitor-override-it&quot;&gt;Letting the visitor override it&lt;/h3&gt;
&lt;p&gt;A time zone picker exists specifically because IP-based detection is not always right, particularly for a visitor on a VPN or traveling. Always leave the field editable and save whatever the visitor explicitly picks over the detected default, without re-detecting and overwriting their choice on a later visit.&lt;/p&gt;
&lt;h3 id=&quot;a-common-mistake-to-avoid&quot;&gt;A common mistake to avoid&lt;/h3&gt;
&lt;p&gt;Do not assume a large country maps to a single time zone just because your example lookups happen to show one clean identifier. Countries spanning multiple zones will return whichever specific zone matches the visitor&amp;#039;s actual location, which is exactly the detail that makes IP-based detection more useful than a country-level guess, but only if your form trusts the specific identifier returned rather than substituting a single default zone for the whole country.&lt;/p&gt;
&lt;h3 id=&quot;detect-once-not-every-visit&quot;&gt;Detect once, not every visit&lt;/h3&gt;
&lt;p&gt;Run the detection once, the first time a visitor sets up their account or preferences, and store the result. Re-running it on every login would waste requests without adding value, since a visitor&amp;#039;s chosen zone, once set deliberately, should stick unless they change it themselves.&lt;/p&gt;
&lt;h3 id=&quot;a-related-endpoint-for-precise-checks&quot;&gt;A related endpoint for precise checks&lt;/h3&gt;
&lt;p&gt;If you already have specific coordinates rather than an IP address, such as a delivery address a visitor typed in, /v1/timezone resolves the zone directly from latitude and longitude and can also accept a specific timestamp, which is useful when you need to know the zone as it applied at a particular moment rather than right now.&lt;/p&gt;
&lt;h3 id=&quot;what-it-costs&quot;&gt;What it costs&lt;/h3&gt;
&lt;p&gt;A single lookup per new account or preferences setup is one request. Even a service signing up a steady stream of new users each day stays well inside the 2,500 free requests a day included with every key or available from a single address without one, using this feature alone.&lt;/p&gt;
&lt;p&gt;Detecting a sensible default and then getting out of the way is the right balance for a time zone picker. Full field details are on the &lt;a href=&quot;/docs/ipv4-lookup/&quot;&gt;IPv4 lookup docs&lt;/a&gt; and the &lt;a href=&quot;/docs/timezone-lookup/&quot;&gt;timezone lookup docs&lt;/a&gt;.&lt;/p&gt;</content></entry>
<entry><title>Restrict form submissions to a delivery radius</title><link href="https://www.mygeocode.com/blog/restrict-form-submissions-to-a-delivery-radius/"/><id>https://www.mygeocode.com/blog/restrict-form-submissions-to-a-delivery-radius/</id><published>2026-09-11T08:44:00+00:00</published><updated>2026-09-11T08:44:00+00:00</updated><category term="guides" label="Guides"/><summary>Geocode a submitted delivery address and reject it automatically when it falls outside a fixed distance from your hub, before an order is ever placed.</summary><content type="html">&lt;p&gt;A delivery service that only covers a certain area needs a way to reject addresses outside that area before an order gets placed, not after a driver discovers it during dispatch.&lt;/p&gt;
&lt;h3 id=&quot;geocoding-the-submitted-address&quot;&gt;Geocoding the submitted address&lt;/h3&gt;
&lt;p&gt;When a customer submits their delivery address, geocode it before accepting the order.&lt;/p&gt;
&lt;div class=&quot;code&quot;&gt;&lt;pre&gt;&lt;code&gt;GET /v1/forward?q=10 Downing Street, London&amp;amp;limit=1&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div class=&quot;code&quot;&gt;&lt;pre&gt;&lt;code&gt;{
  &amp;quot;status&amp;quot;: &amp;quot;ok&amp;quot;,
  &amp;quot;results&amp;quot;: [
    {&amp;quot;formatted&amp;quot;: &amp;quot;10 Downing Street, London, UK&amp;quot;, &amp;quot;lat&amp;quot;: 51.5033, &amp;quot;lon&amp;quot;: -0.1276, &amp;quot;type&amp;quot;: &amp;quot;address&amp;quot;, &amp;quot;precision&amp;quot;: &amp;quot;house&amp;quot;, &amp;quot;confidence&amp;quot;: 0.96, &amp;quot;place_id&amp;quot;: &amp;quot;op678&amp;quot;, &amp;quot;components&amp;quot;: {}}
  ]
}&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3 id=&quot;comparing-against-your-hub&quot;&gt;Comparing against your hub&lt;/h3&gt;
&lt;p&gt;With the customer&amp;#039;s coordinates and your fixed delivery hub coordinates in hand, calculate the straight-line distance between the two using a standard haversine formula in your own code, and compare it against your delivery radius.&lt;/p&gt;
&lt;div class=&quot;code&quot;&gt;&lt;pre&gt;&lt;code&gt;distance_km = haversine(hub_lat, hub_lon, 51.5033, -0.1276)
if distance_km &amp;gt; max_radius_km:
    reject_order()&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3 id=&quot;checking-confidence-before-you-reject&quot;&gt;Checking confidence before you reject&lt;/h3&gt;
&lt;p&gt;A low confidence score on the geocoding result means the address itself was ambiguous, not necessarily that it falls outside your radius. Handle a low-confidence match separately from a clean out-of-radius rejection, perhaps by asking the customer to confirm or refine their address rather than rejecting the order outright.&lt;/p&gt;
&lt;h3 id=&quot;a-second-example-choosing-the-nearest-of-several-hubs&quot;&gt;A second example: choosing the nearest of several hubs&lt;/h3&gt;
&lt;p&gt;A business running more than one delivery hub should calculate distance to every hub, not just one, and accept the order if any hub&amp;#039;s radius covers the address. Assigning the order to whichever hub came back closest also sets up the fulfillment side correctly from the same calculation, since you already have the winning hub&amp;#039;s identity once you have sorted by distance.&lt;/p&gt;
&lt;div class=&quot;code&quot;&gt;&lt;pre&gt;&lt;code&gt;nearest_hub = min(hubs, key=lambda h: haversine(h.lat, h.lon, cust_lat, cust_lon))
if nearest_hub.distance_km &amp;gt; nearest_hub.max_radius_km:
    reject_order()&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3 id=&quot;a-common-mistake-to-avoid&quot;&gt;A common mistake to avoid&lt;/h3&gt;
&lt;p&gt;Do not reject an order the instant distance_km exceeds max_radius_km by a trivial amount, such as a few dozen meters, without giving the customer any explanation or recourse. An address sitting essentially on the boundary line is a normal outcome of drawing a radius on a map, and a flat rejection with no context reads as arbitrary to a customer who lives two streets outside a line they cannot see. Showing the calculated distance and the covered radius, rather than a bare rejection, at least explains why.&lt;/p&gt;
&lt;h3 id=&quot;using-a-straight-line-radius-versus-real-coverage&quot;&gt;Using a straight-line radius versus real coverage&lt;/h3&gt;
&lt;p&gt;A straight-line radius is a simple approximation and will occasionally include a point that is technically far by road, or exclude one that is close by road but far in a straight line, such as across a river with no nearby bridge. For most delivery zone checks this approximation is good enough, and refining it further would require routing data outside what a geocoding lookup provides.&lt;/p&gt;
&lt;h3 id=&quot;what-it-costs&quot;&gt;What it costs&lt;/h3&gt;
&lt;p&gt;Each address check is one geocoding request, or one request per candidate hub compared if you check the address against a hub list through separate calls rather than reusing the same coordinates locally, since only the address itself needs geocoding, not each hub. A delivery form checking every submitted order stays well within the 2,500 free requests a day included with every key for a typical local business, moving onto prepaid credit only once order volume grows substantially.&lt;/p&gt;
&lt;p&gt;Rejecting an out-of-area order at submission time, rather than at dispatch, saves both sides a wasted conversation later. Full parameter details are on the &lt;a href=&quot;/docs/forward-geocoding/&quot;&gt;forward geocoding docs&lt;/a&gt;, and current pricing tiers are on the &lt;a href=&quot;/pricing/&quot;&gt;pricing page&lt;/a&gt;.&lt;/p&gt;</content></entry>
<entry><title>Add a &quot;detect my location&quot; button without the browser prompt</title><link href="https://www.mygeocode.com/blog/add-a-detect-my-location-button-without-the-browser-prompt/"/><id>https://www.mygeocode.com/blog/add-a-detect-my-location-button-without-the-browser-prompt/</id><published>2026-09-10T20:25:00+00:00</published><updated>2026-09-10T20:25:00+00:00</updated><category term="guides" label="Guides"/><summary>Skip the browser&#039;s location permission dialog by detecting an approximate location from the visitor&#039;s IP address entirely on your own server.</summary><content type="html">&lt;p&gt;The browser&amp;#039;s built-in location prompt asks for GPS-level precision and requires an explicit permission click, which a lot of visitors decline on reflex. A &amp;quot;detect my location&amp;quot; button does not have to go through that dialog at all if approximate location is good enough for what you are building.&lt;/p&gt;
&lt;h3 id=&quot;detecting-from-the-server-side&quot;&gt;Detecting from the server side&lt;/h3&gt;
&lt;p&gt;When the button is clicked, submit a request to your own server, which already sees the visitor&amp;#039;s IP address on the connection. Look that address up against /v1/ip and use the result directly, with no browser permission dialog involved at any point.&lt;/p&gt;
&lt;div class=&quot;code&quot;&gt;&lt;pre&gt;&lt;code&gt;GET /v1/ip&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div class=&quot;code&quot;&gt;&lt;pre&gt;&lt;code&gt;{
  &amp;quot;status&amp;quot;: &amp;quot;ok&amp;quot;,
  &amp;quot;ip&amp;quot;: &amp;quot;198.51.100.23&amp;quot;,
  &amp;quot;version&amp;quot;: 4,
  &amp;quot;found&amp;quot;: true,
  &amp;quot;country&amp;quot;: &amp;quot;Italy&amp;quot;,
  &amp;quot;country_code&amp;quot;: &amp;quot;IT&amp;quot;,
  &amp;quot;region&amp;quot;: &amp;quot;Lombardy&amp;quot;,
  &amp;quot;city&amp;quot;: &amp;quot;Milan&amp;quot;,
  &amp;quot;postcode&amp;quot;: &amp;quot;20121&amp;quot;,
  &amp;quot;lat&amp;quot;: 45.4642,
  &amp;quot;lon&amp;quot;: 9.1900,
  &amp;quot;timezone&amp;quot;: &amp;quot;Europe/Rome&amp;quot;,
  &amp;quot;asn&amp;quot;: 3456,
  &amp;quot;org&amp;quot;: &amp;quot;Example ISP&amp;quot;
}&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Calling the endpoint with no ip parameter, as shown here, looks up whichever address made the request, which is exactly the visitor&amp;#039;s own address when your backend calls it on their behalf.&lt;/p&gt;
&lt;h3 id=&quot;setting-expectations-correctly&quot;&gt;Setting expectations correctly&lt;/h3&gt;
&lt;p&gt;IP-based location is city-level at best, not GPS precision. Label the feature honestly, something like &amp;quot;detect my approximate area&amp;quot; rather than implying pinpoint accuracy, so a visitor is not surprised when the result is a city center rather than their exact street.&lt;/p&gt;
&lt;h3 id=&quot;a-second-example-regional-pricing-display&quot;&gt;A second example: regional pricing display&lt;/h3&gt;
&lt;p&gt;The same button pattern works for showing region-appropriate pricing or currency context on a landing page. Since every price is quoted in EUR regardless of visitor location, a button like this would be used to prefill a shipping estimate or a regional contact number rather than to change the currency shown, but the underlying request is identical, one IP lookup returning enough to fill in city and country immediately.&lt;/p&gt;
&lt;h3 id=&quot;where-this-fits-well&quot;&gt;Where this fits well&lt;/h3&gt;
&lt;p&gt;This approach fits store locators, regional content defaults, and shipping estimates, where a city or region is all that is needed. It does not replace GPS location for something like turn-by-turn navigation, where the browser prompt and real device location are genuinely necessary.&lt;/p&gt;
&lt;h3 id=&quot;a-common-mistake-to-avoid&quot;&gt;A common mistake to avoid&lt;/h3&gt;
&lt;p&gt;Do not treat a corporate network or VPN connection as a special error case that needs handling differently from any other lookup. The endpoint will still return a result, just one centered on wherever that network&amp;#039;s registered address happens to be, which can be a different city or even a different country from where the visitor is actually sitting. The right response is the same one you would give for any IP-based estimate: let the visitor override it, rather than trying to detect and specifically flag VPN traffic as unusual.&lt;/p&gt;
&lt;h3 id=&quot;falling-back-gracefully&quot;&gt;Falling back gracefully&lt;/h3&gt;
&lt;p&gt;If found comes back false, which can happen for some address ranges, fall back to asking the visitor to type a location manually rather than showing an error, since the whole point of the button was to save that step, not to introduce a dead end.&lt;/p&gt;
&lt;h3 id=&quot;cost-of-the-feature&quot;&gt;Cost of the feature&lt;/h3&gt;
&lt;p&gt;Each click is one request. A button used occasionally by visitors stays a tiny fraction of the 2,500 free requests a day included with every key or available from a single address without one. Both IPv4 and IPv6 visitors resolve through the same call, so there is no separate code path needed for either version.&lt;/p&gt;
&lt;p&gt;Skipping the permission dialog trades precision for convenience, which is the right trade for a lot of location features. The &lt;a href=&quot;/docs/ipv4-lookup/&quot;&gt;IPv4 lookup docs&lt;/a&gt; cover the endpoint&amp;#039;s full response.&lt;/p&gt;</content></entry>
<entry><title>Parse a free-text address field into structured components</title><link href="https://www.mygeocode.com/blog/parse-a-free-text-address-field-into-structured-components/"/><id>https://www.mygeocode.com/blog/parse-a-free-text-address-field-into-structured-components/</id><published>2026-09-10T07:58:00+00:00</published><updated>2026-09-10T07:58:00+00:00</updated><category term="guides" label="Guides"/><summary>Turn one free-text address string into separate street, city, region, and postal code fields using forward geocoding as a side effect of resolving coordinates.</summary><content type="html">&lt;p&gt;A single free-text address field is easy to collect and hard to work with afterward, especially once you need to filter by city or group by region. Forward geocoding does the parsing for you as a side effect of resolving the address to coordinates.&lt;/p&gt;
&lt;h3 id=&quot;sending-the-free-text&quot;&gt;Sending the free text&lt;/h3&gt;
&lt;div class=&quot;code&quot;&gt;&lt;pre&gt;&lt;code&gt;GET /v1/forward?q=1600 Pennsylvania Avenue, Washington, DC 20500&amp;amp;limit=1&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div class=&quot;code&quot;&gt;&lt;pre&gt;&lt;code&gt;{
  &amp;quot;status&amp;quot;: &amp;quot;ok&amp;quot;,
  &amp;quot;query&amp;quot;: &amp;quot;1600 Pennsylvania Avenue, Washington, DC 20500&amp;quot;,
  &amp;quot;results&amp;quot;: [
    {
      &amp;quot;formatted&amp;quot;: &amp;quot;1600 Pennsylvania Avenue NW, Washington, DC 20500&amp;quot;,
      &amp;quot;lat&amp;quot;: 38.8977,
      &amp;quot;lon&amp;quot;: -77.0365,
      &amp;quot;type&amp;quot;: &amp;quot;address&amp;quot;,
      &amp;quot;precision&amp;quot;: &amp;quot;house&amp;quot;,
      &amp;quot;confidence&amp;quot;: 0.97,
      &amp;quot;place_id&amp;quot;: &amp;quot;def456&amp;quot;,
      &amp;quot;components&amp;quot;: {&amp;quot;house_number&amp;quot;: &amp;quot;1600&amp;quot;, &amp;quot;street&amp;quot;: &amp;quot;Pennsylvania Avenue NW&amp;quot;, &amp;quot;city&amp;quot;: &amp;quot;Washington&amp;quot;, &amp;quot;region&amp;quot;: &amp;quot;DC&amp;quot;, &amp;quot;postcode&amp;quot;: &amp;quot;20500&amp;quot;, &amp;quot;country&amp;quot;: &amp;quot;US&amp;quot;}
    }
  ]
}&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3 id=&quot;storing-components-separately&quot;&gt;Storing components separately&lt;/h3&gt;
&lt;p&gt;Once you have the components object, write each field into its own database column rather than keeping only the original free-text string. This makes it possible to filter customer records by city or region, generate accurate regional reports, and validate that a postal code and city actually belong together, none of which is practical against a single unstructured string.&lt;/p&gt;
&lt;h3 id=&quot;a-second-example-an-address-in-a-different-format&quot;&gt;A second example: an address in a different format&lt;/h3&gt;
&lt;p&gt;Address structure is not the same everywhere. A United Kingdom address might resolve with a county in place of a US-style region, and a component like house_number might be entirely absent for a named building. Sending the same style of request works regardless of country, but the component keys you actually get back can vary, so build your storage schema to tolerate a component being missing rather than assuming every country fills in the exact same set every time.&lt;/p&gt;
&lt;h3 id=&quot;a-common-mistake-to-avoid&quot;&gt;A common mistake to avoid&lt;/h3&gt;
&lt;p&gt;Do not hard-code an assumption that every result will contain all of house_number, street, city, region, and postcode, then treat any address missing one of them as a parsing error. A perfectly valid address, especially outside a house-numbered urban grid, can legitimately come back with some fields empty. Building strict validation around a full set of components will reject real customer input that the endpoint parsed correctly.&lt;/p&gt;
&lt;h3 id=&quot;keeping-the-original-text-too&quot;&gt;Keeping the original text too&lt;/h3&gt;
&lt;p&gt;Store the original free-text input alongside the parsed components rather than discarding it. If a component comes back incomplete or a customer needs to correct something later, having the original text on hand makes re-parsing or manual correction straightforward.&lt;/p&gt;
&lt;h3 id=&quot;handling-partial-parses&quot;&gt;Handling partial parses&lt;/h3&gt;
&lt;p&gt;Not every address resolves with every component filled in. A rural address might come back without a house_number, and a small town might come back without a distinct region value. Treat missing component fields as legitimately empty rather than as a parsing failure, and fall back to the formatted string for display when a specific component you wanted is not present.&lt;/p&gt;
&lt;h3 id=&quot;handling-more-than-one-plausible-match&quot;&gt;Handling more than one plausible match&lt;/h3&gt;
&lt;p&gt;Raising limit above 1 returns several candidate results ordered by how well each matches the free text, which is useful when you want to show the customer a short pick list rather than silently committing to the top match. This is a reasonable middle ground between full automatic parsing and a fully manual entry form, particularly for addresses your confidence threshold would otherwise flag as uncertain.&lt;/p&gt;
&lt;h3 id=&quot;what-it-costs&quot;&gt;What it costs&lt;/h3&gt;
&lt;p&gt;Parsing a free-text field this way costs one request per address, the same as any other forward geocoding lookup. A backfill job cleaning up an existing table of free-text addresses can run through the whole table as a bulk request, one request per row.&lt;/p&gt;
&lt;p&gt;Turning free text into structured fields is a natural side effect of geocoding an address, not an extra step. The &lt;a href=&quot;/docs/forward-geocoding/&quot;&gt;forward geocoding docs&lt;/a&gt; list every component the endpoint can return.&lt;/p&gt;</content></entry>
<entry><title>Set up polling for a large batch job without a webhook</title><link href="https://www.mygeocode.com/blog/set-up-polling-for-a-large-batch-job-without-a-webhook/"/><id>https://www.mygeocode.com/blog/set-up-polling-for-a-large-batch-job-without-a-webhook/</id><published>2026-09-09T22:23:00+00:00</published><updated>2026-09-09T22:23:00+00:00</updated><category term="guides" label="Guides"/><summary>There is no async job queue here, just synchronous bulk requests, so a large job means chunking and polling your own quota headers.</summary><content type="html">&lt;p&gt;Some APIs hand back a job ID for a large batch and expect you to poll or wait for a webhook while it processes in the background. This one does not work that way. Every bulk request, large or small, is answered synchronously in the same call, which changes how you should think about running a very large job.&lt;/p&gt;
&lt;h3 id=&quot;chunking-instead-of-queuing&quot;&gt;Chunking instead of queuing&lt;/h3&gt;
&lt;p&gt;Rather than submitting one enormous array and waiting, split a large job into fixed-size chunks, a few hundred to a few thousand items each, and send them as a sequence of ordinary bulk POST requests. Since each call returns its results immediately, there is no job status to poll for, only the next chunk to send.&lt;/p&gt;
&lt;div class=&quot;code&quot;&gt;&lt;pre&gt;&lt;code&gt;POST /v1/forward
Content-Type: application/json

[&amp;quot;address 1&amp;quot;, &amp;quot;address 2&amp;quot;, &amp;quot;... up to a few hundred items&amp;quot;]&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3 id=&quot;what-polling-actually-means-here&quot;&gt;What &amp;quot;polling&amp;quot; actually means here&lt;/h3&gt;
&lt;p&gt;The closest thing to polling in this setup is checking your own quota headers between chunks rather than checking a job status. Read X-Quota-Free-Remaining and X-Credits-Remaining after each chunk completes, and pause the job, or stop it, if you are about to run past your daily free allowance without enough prepaid credit to continue.&lt;/p&gt;
&lt;div class=&quot;code&quot;&gt;&lt;pre&gt;&lt;code&gt;X-Quota-Free-Remaining: 340
X-Credits-Remaining: 12.50&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3 id=&quot;building-a-simple-runner&quot;&gt;Building a simple runner&lt;/h3&gt;
&lt;p&gt;A small script that loops over chunks, sends each one, checks the headers on the response, and either continues or pauses based on what it sees is all a large batch job needs here. There is no separate job status endpoint to call, since the chunk you just sent already contains everything you asked for.&lt;/p&gt;
&lt;h3 id=&quot;resuming-after-a-pause&quot;&gt;Resuming after a pause&lt;/h3&gt;
&lt;p&gt;Track which chunk index you have successfully processed so far, so a pause for a quota reset or a credit top-up can resume exactly where it left off rather than reprocessing earlier chunks and spending requests twice.&lt;/p&gt;
&lt;h3 id=&quot;what-it-costs&quot;&gt;What it costs&lt;/h3&gt;
&lt;p&gt;Cost is the same either way, one request per item across the whole job. The chunking approach only changes how you manage the job, not how many requests it uses in total.&lt;/p&gt;
&lt;p&gt;Treating a large job as a sequence of ordinary synchronous chunks, rather than looking for an async job system that does not exist here, keeps the whole thing simple. The &lt;a href=&quot;/docs/rate-limits/&quot;&gt;rate limits docs&lt;/a&gt; cover the quota headers that drive this pattern.&lt;/p&gt;</content></entry>
<entry><title>Calculate distance between two geocoded points</title><link href="https://www.mygeocode.com/blog/calculate-distance-between-two-geocoded-points/"/><id>https://www.mygeocode.com/blog/calculate-distance-between-two-geocoded-points/</id><published>2026-09-09T08:56:00+00:00</published><updated>2026-09-09T08:56:00+00:00</updated><category term="guides" label="Guides"/><summary>Geocode two addresses to get their coordinates, then calculate the distance between them using standard great-circle math you already have in your own code.</summary><content type="html">&lt;p&gt;Distance between two addresses is not something a geocoding API returns directly, since a distance calculation only needs coordinates, which the forward endpoint already gives you for any address you send it.&lt;/p&gt;
&lt;h3 id=&quot;getting-coordinates-for-both-addresses&quot;&gt;Getting coordinates for both addresses&lt;/h3&gt;
&lt;p&gt;Geocode each address separately, or together in a single bulk request, to get a latitude and longitude pair for each.&lt;/p&gt;
&lt;div class=&quot;code&quot;&gt;&lt;pre&gt;&lt;code&gt;POST /v1/forward
Content-Type: application/json

[&amp;quot;221B Baker Street, London&amp;quot;, &amp;quot;10 Downing Street, London&amp;quot;]&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div class=&quot;code&quot;&gt;&lt;pre&gt;&lt;code&gt;{
  &amp;quot;status&amp;quot;: &amp;quot;ok&amp;quot;,
  &amp;quot;results&amp;quot;: [
    {&amp;quot;formatted&amp;quot;: &amp;quot;221B Baker Street, London, UK&amp;quot;, &amp;quot;lat&amp;quot;: 51.5237, &amp;quot;lon&amp;quot;: -0.1585, &amp;quot;type&amp;quot;: &amp;quot;address&amp;quot;, &amp;quot;precision&amp;quot;: &amp;quot;house&amp;quot;, &amp;quot;confidence&amp;quot;: 0.95, &amp;quot;place_id&amp;quot;: &amp;quot;abc123&amp;quot;, &amp;quot;components&amp;quot;: {}},
    {&amp;quot;formatted&amp;quot;: &amp;quot;10 Downing Street, London, UK&amp;quot;, &amp;quot;lat&amp;quot;: 51.5033, &amp;quot;lon&amp;quot;: -0.1276, &amp;quot;type&amp;quot;: &amp;quot;address&amp;quot;, &amp;quot;precision&amp;quot;: &amp;quot;house&amp;quot;, &amp;quot;confidence&amp;quot;: 0.96, &amp;quot;place_id&amp;quot;: &amp;quot;op678&amp;quot;, &amp;quot;components&amp;quot;: {}}
  ]
}&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3 id=&quot;calculating-the-distance&quot;&gt;Calculating the distance&lt;/h3&gt;
&lt;p&gt;With two coordinate pairs in hand, apply the standard haversine formula, which converts the difference in latitude and longitude into a straight-line distance across the Earth&amp;#039;s surface. Most programming languages have a small, well-tested haversine implementation available as a short function or a common library, so there is no need to write the trigonometry from scratch.&lt;/p&gt;
&lt;div class=&quot;code&quot;&gt;&lt;pre&gt;&lt;code&gt;distance_km = haversine(51.5237, -0.1585, 51.5033, -0.1276)&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3 id=&quot;a-common-mistake-to-avoid&quot;&gt;A common mistake to avoid&lt;/h3&gt;
&lt;p&gt;The haversine formula expects latitude and longitude in radians, but the coordinates in an API response are always in degrees. Plugging degree values straight into a formula written for radians will produce a distance that looks plausible at a glance but is quietly wrong by a large factor. Convert degrees to radians first, or use a library function that documents which unit it expects, and check the result against a distance you already know before trusting it in production.&lt;/p&gt;
&lt;h3 id=&quot;a-second-example-total-length-of-a-multi-stop-route&quot;&gt;A second example: total length of a multi-stop route&lt;/h3&gt;
&lt;p&gt;The same calculation extends naturally to more than two points. Geocode every stop on a route in one bulk request, then sum the haversine distance between each consecutive pair of coordinates to get a rough total route length, useful for a quick delivery route estimate even before you have real turn-by-turn routing in place.&lt;/p&gt;
&lt;div class=&quot;code&quot;&gt;&lt;pre&gt;&lt;code&gt;total_km = haversine(a, b) + haversine(b, c) + haversine(c, d)&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3 id=&quot;straight-line-versus-travel-distance&quot;&gt;Straight-line versus travel distance&lt;/h3&gt;
&lt;p&gt;A haversine calculation gives straight-line distance, not driving or walking distance along real roads. For most uses, such as sorting nearby locations or estimating rough proximity, straight-line distance is good enough and needs nothing beyond the coordinates you already have. If you specifically need routed travel distance, that is a separate kind of calculation outside what a geocoding lookup provides.&lt;/p&gt;
&lt;h3 id=&quot;an-edge-case-worth-knowing&quot;&gt;An edge case worth knowing&lt;/h3&gt;
&lt;p&gt;Two points on opposite sides of the antimeridian, or very close to a pole, can trip up a naive distance formula that does not account for longitude wrapping around from 180 to negative 180. This is rare for typical address-to-address distance checks, but worth testing for if your application legitimately spans that part of the globe.&lt;/p&gt;
&lt;h3 id=&quot;request-cost&quot;&gt;Request cost&lt;/h3&gt;
&lt;p&gt;Geocoding two addresses costs two requests, whether sent as separate calls or one bulk call. The distance math itself, once you have both coordinate pairs, happens entirely in your own code and costs nothing further against your allowance.&lt;/p&gt;
&lt;h3 id=&quot;reusing-coordinates-you-already-have&quot;&gt;Reusing coordinates you already have&lt;/h3&gt;
&lt;p&gt;If either address is one you have geocoded before and cached, only the new address needs a fresh request, cutting the cost for that particular calculation to one request instead of two.&lt;/p&gt;
&lt;p&gt;Distance calculations are one geocoding request away for any address you have not already resolved to coordinates. See the &lt;a href=&quot;/docs/forward-geocoding/&quot;&gt;forward geocoding docs&lt;/a&gt; for the full response shape.&lt;/p&gt;</content></entry>
<entry><title>Build a store locator with reverse geocoding</title><link href="https://www.mygeocode.com/blog/build-a-store-locator-with-reverse-geocoding/"/><id>https://www.mygeocode.com/blog/build-a-store-locator-with-reverse-geocoding/</id><published>2026-09-08T21:50:00+00:00</published><updated>2026-09-08T21:50:00+00:00</updated><category term="guides" label="Guides"/><summary>Turn a customer&#039;s coordinates into a readable address with reverse geocoding, then compare it against your store list to surface the closest locations first.</summary><content type="html">&lt;p&gt;A store locator needs two things working together: a way to know where the customer is, and a list of store coordinates to compare against. Reverse geocoding covers the first part cleanly.&lt;/p&gt;
&lt;h3 id=&quot;turning-coordinates-into-a-readable-location&quot;&gt;Turning coordinates into a readable location&lt;/h3&gt;
&lt;p&gt;Once you have the customer&amp;#039;s coordinates, from a location they entered or from an IP lookup, reverse geocode them to show a readable place name back to the customer, confirming the search location before showing results.&lt;/p&gt;
&lt;div class=&quot;code&quot;&gt;&lt;pre&gt;&lt;code&gt;GET /v1/reverse?lat=51.5074&amp;amp;lon=-0.1278&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div class=&quot;code&quot;&gt;&lt;pre&gt;&lt;code&gt;{
  &amp;quot;status&amp;quot;: &amp;quot;ok&amp;quot;,
  &amp;quot;formatted&amp;quot;: &amp;quot;Trafalgar Square, London, UK&amp;quot;,
  &amp;quot;lat&amp;quot;: 51.5074,
  &amp;quot;lon&amp;quot;: -0.1278,
  &amp;quot;type&amp;quot;: &amp;quot;address&amp;quot;,
  &amp;quot;precision&amp;quot;: &amp;quot;street&amp;quot;,
  &amp;quot;confidence&amp;quot;: 0.9,
  &amp;quot;place_id&amp;quot;: &amp;quot;lm345&amp;quot;,
  &amp;quot;components&amp;quot;: {&amp;quot;city&amp;quot;: &amp;quot;London&amp;quot;, &amp;quot;country&amp;quot;: &amp;quot;GB&amp;quot;}
}&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3 id=&quot;finding-the-nearest-stores&quot;&gt;Finding the nearest stores&lt;/h3&gt;
&lt;p&gt;The API returns coordinates, not distances between two arbitrary points, so once you have the customer&amp;#039;s latitude and longitude, calculate distance to each store in your own list using standard great-circle distance math on the two coordinate pairs. Sort your store list by that calculated distance and show the closest few.&lt;/p&gt;
&lt;h3 id=&quot;letting-customers-type-an-address-instead&quot;&gt;Letting customers type an address instead&lt;/h3&gt;
&lt;p&gt;Not every visitor will share their location. For a typed address search, geocode the entered text with /v1/forward first to get coordinates, then run the same nearest-store distance calculation against that result. If you want suggestions to appear as the customer types rather than waiting for a full address, /v1/autocomplete can back a type-ahead field that feeds a place_id or resolved text into the same forward geocoding step.&lt;/p&gt;
&lt;h3 id=&quot;a-second-example-a-mobile-app-using-device-gps&quot;&gt;A second example: a mobile app using device GPS&lt;/h3&gt;
&lt;p&gt;A native app can skip the reverse geocoding step for finding nearby stores, since it already has raw device coordinates to compare against your store list directly. Reverse geocoding still earns its place there for the confirmation text shown to the customer, something like &amp;quot;Showing stores near Trafalgar Square, London&amp;quot; rather than a bare pair of numbers, which reassures the customer that the app understood their location correctly before they scroll through results.&lt;/p&gt;
&lt;h3 id=&quot;a-common-mistake-to-avoid&quot;&gt;A common mistake to avoid&lt;/h3&gt;
&lt;p&gt;Do not assume every typed address geocodes to house-level precision before running the distance calculation. A broad query like just a city name will still return a result, but at a coarser precision, and treating that coarse point as if it pinpointed the customer&amp;#039;s exact building will make your &amp;quot;nearest store&amp;quot; ranking less reliable than the confidence and precision fields would have told you to expect.&lt;/p&gt;
&lt;h3 id=&quot;handling-a-tie-between-two-nearby-stores&quot;&gt;Handling a tie between two nearby stores&lt;/h3&gt;
&lt;p&gt;When two stores come back at nearly identical calculated distances, distance alone will not tell the customer which one actually suits them better. Showing both, along with details like opening hours or stock availability, gives the customer the deciding factor rather than an arbitrary sort order.&lt;/p&gt;
&lt;h3 id=&quot;confirming-the-match&quot;&gt;Confirming the match&lt;/h3&gt;
&lt;p&gt;Show the formatted address from the reverse geocoding result alongside the store results, so the customer can immediately see whether the location that was detected or resolved matches where they actually meant to search from, and can correct it if not.&lt;/p&gt;
&lt;h3 id=&quot;what-it-costs&quot;&gt;What it costs&lt;/h3&gt;
&lt;p&gt;Each search is one reverse or forward geocoding request, regardless of how many stores you compare it against afterward, since the distance calculation itself happens in your own code once you have coordinates for both sides. A store locator getting a moderate amount of daily traffic fits easily within the 2,500 free requests a day included with every key.&lt;/p&gt;
&lt;p&gt;A store locator built this way needs exactly one geocoding request per customer search, with everything after that handled locally against your own store list. Full response fields are on the &lt;a href=&quot;/docs/reverse-geocoding/&quot;&gt;reverse geocoding docs&lt;/a&gt;.&lt;/p&gt;</content></entry>
<entry><title>Add a country dropdown that defaults from IP</title><link href="https://www.mygeocode.com/blog/add-a-country-dropdown-that-defaults-from-ip/"/><id>https://www.mygeocode.com/blog/add-a-country-dropdown-that-defaults-from-ip/</id><published>2026-09-08T08:48:00+00:00</published><updated>2026-09-08T08:48:00+00:00</updated><category term="guides" label="Guides"/><summary>Pre-select the right country in a signup or checkout dropdown using a visitor&#039;s IP address, saving a scroll through a long list without ever removing their choice.</summary><content type="html">&lt;p&gt;A country dropdown that defaults to whatever a visitor&amp;#039;s IP address suggests saves most people a scroll through a long list, as long as it still lets them change it when the default is wrong.&lt;/p&gt;
&lt;h3 id=&quot;getting-the-default-value&quot;&gt;Getting the default value&lt;/h3&gt;
&lt;p&gt;Look up the visitor&amp;#039;s IP address on your server when the page is requested, and read the country_code field from the response to set as the dropdown&amp;#039;s selected option before the page is even sent to the browser.&lt;/p&gt;
&lt;div class=&quot;code&quot;&gt;&lt;pre&gt;&lt;code&gt;GET /v1/ip?ip=192.0.2.15&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div class=&quot;code&quot;&gt;&lt;pre&gt;&lt;code&gt;{
  &amp;quot;status&amp;quot;: &amp;quot;ok&amp;quot;,
  &amp;quot;ip&amp;quot;: &amp;quot;192.0.2.15&amp;quot;,
  &amp;quot;version&amp;quot;: 4,
  &amp;quot;found&amp;quot;: true,
  &amp;quot;country&amp;quot;: &amp;quot;Spain&amp;quot;,
  &amp;quot;country_code&amp;quot;: &amp;quot;ES&amp;quot;,
  &amp;quot;region&amp;quot;: &amp;quot;Madrid&amp;quot;,
  &amp;quot;city&amp;quot;: &amp;quot;Madrid&amp;quot;,
  &amp;quot;postcode&amp;quot;: &amp;quot;28001&amp;quot;,
  &amp;quot;lat&amp;quot;: 40.4168,
  &amp;quot;lon&amp;quot;: -3.7038,
  &amp;quot;timezone&amp;quot;: &amp;quot;Europe/Madrid&amp;quot;,
  &amp;quot;asn&amp;quot;: 5678,
  &amp;quot;org&amp;quot;: &amp;quot;Example Networks&amp;quot;
}&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3 id=&quot;rendering-it-server-side&quot;&gt;Rendering it server-side&lt;/h3&gt;
&lt;p&gt;Since this happens entirely server-side, before rendering the page, there is no flash of a wrong default followed by a correction in the browser, which is what happens with client-side approaches that call out after the page has already loaded. Set the selected attribute on the matching option directly in the HTML you generate, matching country_code against whatever ISO codes your option values already use.&lt;/p&gt;
&lt;h3 id=&quot;a-second-example-prefilling-a-checkout-country-field&quot;&gt;A second example: prefilling a checkout country field&lt;/h3&gt;
&lt;p&gt;The same call works just as well on a checkout page as on a signup form. Look up the shopper&amp;#039;s IP once when the checkout page loads, match country_code to your shipping country list, and select it by default. Because the shipping country affects tax and delivery options shown further down the page, getting this right before the page renders avoids a jarring reflow later if the visitor had to correct it after everything else had already loaded around a wrong default.&lt;/p&gt;
&lt;h3 id=&quot;always-allow-a-change&quot;&gt;Always allow a change&lt;/h3&gt;
&lt;p&gt;Treat the IP-based country as a starting point, not a locked-in answer. A visitor traveling, using a work VPN, or simply living somewhere their network provider&amp;#039;s registered address does not match will need to pick a different option, so nothing about the form should assume the default is correct.&lt;/p&gt;
&lt;h3 id=&quot;a-common-mistake-to-avoid&quot;&gt;A common mistake to avoid&lt;/h3&gt;
&lt;p&gt;Do not cache the fully rendered page behind a CDN or reverse proxy without varying it by visitor, since a page rendered once for the first visitor&amp;#039;s IP and then served from cache to everyone afterward will show that first visitor&amp;#039;s country to every subsequent visitor regardless of where they actually are. If your pages are cached at all, either exclude this fragment from the cache or render it through a small server-side include that runs per request.&lt;/p&gt;
&lt;h3 id=&quot;handling-a-lookup-that-finds-nothing&quot;&gt;Handling a lookup that finds nothing&lt;/h3&gt;
&lt;p&gt;If found comes back false, which happens for some private or unallocated ranges, fall back to a neutral default, such as an empty selection or your most common country, rather than leaving the field in an undefined state.&lt;/p&gt;
&lt;h3 id=&quot;ipv6-visitors-work-the-same-way&quot;&gt;IPv6 visitors work the same way&lt;/h3&gt;
&lt;p&gt;A visitor connecting over IPv6 gets the same country_code field back from the same endpoint, just resolved against a /48 network rather than a /24. Nothing about the dropdown logic needs to change based on which IP version made the request. See the &lt;a href=&quot;/docs/ipv6-lookup/&quot;&gt;IPv6 lookup docs&lt;/a&gt; if you want the version-specific details.&lt;/p&gt;
&lt;h3 id=&quot;cost-of-this-feature&quot;&gt;Cost of this feature&lt;/h3&gt;
&lt;p&gt;This is one request per new visitor session, cached for the session so a single visit does not trigger repeated lookups across multiple form pages. That keeps the feature well within the 2,500 free requests a day included with every key, or available from a single address without one, for any but the busiest sites.&lt;/p&gt;
&lt;p&gt;A sensible default saves a scroll through a hundred-item list, and the visitor still has full control if it guesses wrong. Field details are on the &lt;a href=&quot;/docs/ipv4-lookup/&quot;&gt;IPv4 lookup docs&lt;/a&gt;.&lt;/p&gt;</content></entry>
<entry><title>Filter bot traffic using ASN and network data</title><link href="https://www.mygeocode.com/blog/filter-bot-traffic-using-asn-and-network-data/"/><id>https://www.mygeocode.com/blog/filter-bot-traffic-using-asn-and-network-data/</id><published>2026-09-07T19:43:00+00:00</published><updated>2026-09-07T19:43:00+00:00</updated><category term="guides" label="Guides"/><summary>Data center and hosting networks show up clearly in ASN and organization fields, which is useful signal for filtering automated traffic.</summary><content type="html">&lt;p&gt;Most real visitors connect from residential or mobile networks. Most automated traffic connects from data centers and cloud hosting providers. That difference shows up plainly in the asn and org fields on an IP lookup.&lt;/p&gt;
&lt;h3 id=&quot;getting-network-data&quot;&gt;Getting network data&lt;/h3&gt;
&lt;div class=&quot;code&quot;&gt;&lt;pre&gt;&lt;code&gt;GET /v1/ip?ip=203.0.113.99&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div class=&quot;code&quot;&gt;&lt;pre&gt;&lt;code&gt;{
  &amp;quot;status&amp;quot;: &amp;quot;ok&amp;quot;,
  &amp;quot;ip&amp;quot;: &amp;quot;203.0.113.99&amp;quot;,
  &amp;quot;version&amp;quot;: 4,
  &amp;quot;found&amp;quot;: true,
  &amp;quot;country&amp;quot;: &amp;quot;United States&amp;quot;,
  &amp;quot;country_code&amp;quot;: &amp;quot;US&amp;quot;,
  &amp;quot;region&amp;quot;: &amp;quot;Virginia&amp;quot;,
  &amp;quot;city&amp;quot;: &amp;quot;Ashburn&amp;quot;,
  &amp;quot;postcode&amp;quot;: &amp;quot;20147&amp;quot;,
  &amp;quot;lat&amp;quot;: 39.0438,
  &amp;quot;lon&amp;quot;: -77.4874,
  &amp;quot;timezone&amp;quot;: &amp;quot;America/New_York&amp;quot;,
  &amp;quot;asn&amp;quot;: 16509,
  &amp;quot;org&amp;quot;: &amp;quot;Example Cloud Hosting&amp;quot;
}&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3 id=&quot;building-a-simple-filter&quot;&gt;Building a simple filter&lt;/h3&gt;
&lt;p&gt;Maintain a short list of organization name patterns or specific ASN numbers you associate with cloud hosting and known automation platforms, based on your own traffic history, and flag traffic from those networks for extra scrutiny, such as a CAPTCHA challenge or a lower trust score, rather than blocking outright. Legitimate uses of cloud IP ranges exist, including corporate VPNs and mobile carrier infrastructure, so this signal works best combined with behavioral signals rather than used alone.&lt;/p&gt;
&lt;h3 id=&quot;a-second-signal-country-mismatch&quot;&gt;A second signal: country mismatch&lt;/h3&gt;
&lt;p&gt;Combining the ASN check with the country_code field catches a different pattern. An account that claims to be based in one country but consistently signs in from a data center address registered in another is a stronger signal together than either fact is alone. Neither field on its own proves anything, since real customers travel and use VPNs for entirely ordinary reasons, but the combination is worth weighting more heavily in a scoring system than either check run in isolation.&lt;/p&gt;
&lt;h3 id=&quot;where-this-fits-in-a-request&quot;&gt;Where this fits in a request&lt;/h3&gt;
&lt;p&gt;Run the ASN check at the point traffic first hits your application, such as a signup form or a login attempt, rather than after the fact, so the signal can actually influence what happens next. Cache the result for the length of a session, since an address&amp;#039;s ASN does not change mid-session.&lt;/p&gt;
&lt;h3 id=&quot;handling-returning-visitors&quot;&gt;Handling returning visitors&lt;/h3&gt;
&lt;p&gt;A visitor who was scored once during signup and returns later from the same network address does not need a fresh lookup every time, since the asn and org fields for a given address remain stable across sessions. Cache the classification against the address itself, not just the session, so a known-safe residential address is not re-evaluated on every single future visit either.&lt;/p&gt;
&lt;h3 id=&quot;a-mistake-worth-avoiding&quot;&gt;A mistake worth avoiding&lt;/h3&gt;
&lt;p&gt;Blocking every request from a recognized hosting or cloud ASN outright, rather than scoring it, shuts out a real slice of legitimate traffic along with the automated traffic you actually meant to stop. Corporate VPN exits, some mobile carriers, and privacy-focused browsers all route through infrastructure that can register the same way a hosting provider does. Use the ASN and org fields as one input into a scoring or challenge decision, not as an automatic block rule on their own.&lt;/p&gt;
&lt;h3 id=&quot;what-it-costs&quot;&gt;What it costs&lt;/h3&gt;
&lt;p&gt;One lookup per new session, cached afterward, is one request per visitor rather than one per request they make. On a site processing meaningful signup or login volume, this stays inside the 2,500 free requests a day included with every key for moderate traffic, and moves onto prepaid credit or an Unlimited key once volume grows past that.&lt;/p&gt;
&lt;p&gt;Network origin is one signal among several, not a verdict on its own, but it is a cheap one to add to a signup or login flow. The &lt;a href=&quot;/docs/ipv4-lookup/&quot;&gt;IPv4 lookup docs&lt;/a&gt; and &lt;a href=&quot;/docs/ipv6-lookup/&quot;&gt;IPv6 lookup docs&lt;/a&gt; describe every field the endpoint returns.&lt;/p&gt;</content></entry>
<entry><title>Batch resolve postal codes for a customer list</title><link href="https://www.mygeocode.com/blog/batch-resolve-postal-codes-for-a-customer-list/"/><id>https://www.mygeocode.com/blog/batch-resolve-postal-codes-for-a-customer-list/</id><published>2026-09-07T08:13:00+00:00</published><updated>2026-09-07T08:13:00+00:00</updated><category term="guides" label="Guides"/><summary>Resolve every postal code in a customer export in one batch request, instead of looking up each row individually one at a time.</summary><content type="html">&lt;p&gt;A customer export with a postal code column but no coordinates is a common starting point for anything involving shipping zones, regional reporting, or store assignment. Resolving the whole column at once is faster than writing a loop.&lt;/p&gt;
&lt;h3 id=&quot;sending-the-batch&quot;&gt;Sending the batch&lt;/h3&gt;
&lt;p&gt;POST an array of code and country objects to /v1/postcode. Each item resolves independently and comes back in the same order.&lt;/p&gt;
&lt;div class=&quot;code&quot;&gt;&lt;pre&gt;&lt;code&gt;POST /v1/postcode
Content-Type: application/json

[{&amp;quot;code&amp;quot;: &amp;quot;10115&amp;quot;, &amp;quot;country&amp;quot;: &amp;quot;DE&amp;quot;}, {&amp;quot;code&amp;quot;: &amp;quot;75001&amp;quot;, &amp;quot;country&amp;quot;: &amp;quot;FR&amp;quot;}]&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div class=&quot;code&quot;&gt;&lt;pre&gt;&lt;code&gt;{
  &amp;quot;status&amp;quot;: &amp;quot;ok&amp;quot;,
  &amp;quot;results&amp;quot;: [
    {&amp;quot;postcode&amp;quot;: &amp;quot;10115&amp;quot;, &amp;quot;country_code&amp;quot;: &amp;quot;DE&amp;quot;, &amp;quot;results&amp;quot;: [{&amp;quot;lat&amp;quot;: 52.5300, &amp;quot;lon&amp;quot;: 13.3800, &amp;quot;components&amp;quot;: {&amp;quot;city&amp;quot;: &amp;quot;Berlin&amp;quot;, &amp;quot;region&amp;quot;: &amp;quot;Berlin&amp;quot;}}]},
    {&amp;quot;postcode&amp;quot;: &amp;quot;75001&amp;quot;, &amp;quot;country_code&amp;quot;: &amp;quot;FR&amp;quot;, &amp;quot;results&amp;quot;: [{&amp;quot;lat&amp;quot;: 48.8630, &amp;quot;lon&amp;quot;: 2.3360, &amp;quot;components&amp;quot;: {&amp;quot;city&amp;quot;: &amp;quot;Paris&amp;quot;, &amp;quot;region&amp;quot;: &amp;quot;Ile-de-France&amp;quot;}}]}
  ]
}&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3 id=&quot;matching-results-back-to-customer-records&quot;&gt;Matching results back to customer records&lt;/h3&gt;
&lt;p&gt;Keep the original row index or customer ID alongside each code and country pair before sending the request, and match results back onto customer records by that same position, the same approach that works for bulk forward or reverse geocoding.&lt;/p&gt;
&lt;h3 id=&quot;deduplicating-before-you-send&quot;&gt;Deduplicating before you send&lt;/h3&gt;
&lt;p&gt;A customer list of any real size tends to have far fewer distinct postal codes than it has rows, since many customers share the same code in any given city or neighborhood. Build a unique list of code and country pairs first, resolve that smaller list in one batch, and then map the results back onto every customer row that shares each code, rather than sending one array entry per customer and paying for the same code and country pair over and over.&lt;/p&gt;
&lt;h3 id=&quot;handling-codes-that-do-not-resolve&quot;&gt;Handling codes that do not resolve&lt;/h3&gt;
&lt;p&gt;An empty results array for a particular item usually means the code and country pair does not exist, which is common in an old export with typos or an outdated postal code. Flag those rows for manual review rather than silently dropping them from your report.&lt;/p&gt;
&lt;h3 id=&quot;an-edge-case-formatting-differences&quot;&gt;An edge case: formatting differences&lt;/h3&gt;
&lt;p&gt;A code stored as &amp;quot;SW1A1AA&amp;quot; without a space and one stored as &amp;quot;SW1A 1AA&amp;quot; with one may or may not resolve the same way depending on how consistently your own export formats codes, so normalizing spacing and case in your export before building the batch array reduces the number of rows that fail to resolve purely on formatting rather than on the code being genuinely wrong.&lt;/p&gt;
&lt;h3 id=&quot;what-it-costs&quot;&gt;What it costs&lt;/h3&gt;
&lt;p&gt;A batch is billed one request per item, so a 2,000 row customer list costs 2,000 requests, whether sent as one bulk call or as 2,000 separate ones. Sending it as one call is still worth doing for the reduced overhead and the single set of quota headers to check, even though the request count against your allowance is identical either way. Deduplicating first, as above, is what actually reduces the request count, not the choice between bulk and individual calls.&lt;/p&gt;
&lt;h3 id=&quot;watching-your-allowance-mid-batch&quot;&gt;Watching your allowance mid batch&lt;/h3&gt;
&lt;p&gt;For a list large enough to run past the free 2,500 requests a day, check X-Quota-Free-Remaining before you commit to running the whole thing in one sitting, or split it across a couple of days if you are not planning to move onto prepaid credit for a one-off job.&lt;/p&gt;
&lt;p&gt;Resolving a whole customer list&amp;#039;s postal codes in a single call turns what used to be a slow background job into one straightforward request. The &lt;a href=&quot;/docs/postal-code-lookup/&quot;&gt;postal code lookup docs&lt;/a&gt; cover the full field list.&lt;/p&gt;</content></entry>
</feed>
