Skip to main content

Geohash Encode, Decode & Neighbors API

REAL DATA

Encode coordinates to a geohash at any precision, decode a hash to its exact cell bounds and center, and list its eight neighbors.

Authoritative reference data or standards computation

Base URL
/api/geohash
Capabilities
3 routes
Last updated
July 30, 2026
Data source

GET /api/geohash/encode

Live requestRuns against the public API
No key required
GET/api/geohash/encode?lat=55.6761&lng=12.5683

Request parameters

Latitude in decimal degrees, -90 to 90 inclusive. CTA-5009-A §7 makes the range half-open; we accept exactly 90 and put it in the northernmost cell instead of rejecting it.

Longitude in decimal degrees, -180 to 180 inclusive. CTA-5009-A §7 makes the range half-open; we accept exactly 180, put it in the easternmost cell and never normalize it to -180.

Hash length in characters, 1–12 (5 bits each). Defaults to 9, about 4.8 m × 4.7 m at the equator; above 12 is a 400.

Advanced response options4 options

Return only these fields (comma-separated). Mutually exclusive with 'exclude'.

Return all fields except these (comma-separated).

Pretty-print the JSON response.

Drop the envelope: return the raw array/object without data/meta wrapper.

Response

Example parameters are ready. Send the request to inspect the live response.

Route reference

Code samples Ready-to-copy requests in 4 languages
Choose a code sample language
curl "https://randomapi.dev/api/geohash/encode?lat=55.6761&lng=12.5683"
const res = await fetch("https://randomapi.dev/api/geohash/encode?lat=55.6761&lng=12.5683");
const { data, meta } = await res.json();
import requests

data = requests.get("https://randomapi.dev/api/geohash/encode?lat=55.6761&lng=12.5683").json()["data"]
$json = json_decode(file_get_contents(
  "https://randomapi.dev/api/geohash/encode?lat=55.6761&lng=12.5683"
), true);
$data = $json["data"];
Parameters Route-specific request inputs 3
lat float required

Latitude in decimal degrees, -90 to 90 inclusive. CTA-5009-A §7 makes the range half-open; we accept exactly 90 and put it in the northernmost cell instead of rejecting it.

allowed: -90 – 90
example: lat=55.6761
lng float required

Longitude in decimal degrees, -180 to 180 inclusive. CTA-5009-A §7 makes the range half-open; we accept exactly 180, put it in the easternmost cell and never normalize it to -180.

allowed: -180 – 180
example: lng=12.5683
precision int

Hash length in characters, 1–12 (5 bits each). Defaults to 9, about 4.8 m × 4.7 m at the equator; above 12 is a 400.

default: 9
allowed: 1 – 12
example: precision=5
Universal parameters Shared response and formatting options 4
fields list

Return only these fields (comma-separated). Mutually exclusive with 'exclude'.

example: fields=hash,precision
exclude list

Return all fields except these (comma-separated).

example: exclude=approxCellMetersAtEquator
pretty boolean

Pretty-print the JSON response.

default: false
example: pretty=true
unwrap boolean

Drop the envelope: return the raw array/object without data/meta wrapper.

default: false
example: unwrap=true
Response schema Fields returned in each record 6
hash string

The geohash this cell belongs to, exactly as validated.

example: ezs42

precision integer (1-12)

Hash length in characters; each character adds 5 bits.

example: 5

cell object

Exact cell rectangle in decimal degrees: south, west, north, east. Bounds are binary fractions of the ±90/±180 ranges, so they are exact, not rounded.

example: {"south":42.5830078125,"west":-5.625,"north":42.626953125,"east":-5.5810546875}

center object

Midpoint of the cell as lat/lng — the best single-point estimate, not a stored coordinate.

example: {"lat":42.60498046875,"lng":-5.60302734375}

error object

Half the cell's height and width in degrees: the maximum error of using center as a point.

example: {"latDegrees":0.02197265625,"lngDegrees":0.02197265625}

approxCellMetersAtEquator object

Approximate cell width and height in meters AT THE EQUATOR (111319.5 m per degree of longitude, 110574.3 m per degree of latitude), rounded to 4 decimals. Width shrinks with the cosine of latitude elsewhere.

example: {"width":4891.9698,"height":4859.2211}

Documented examples

Build-generated requests and complete responses
3
Encode Copenhagen at the default precision
GET /api/geohash/encode?lat=55.6761&lng=12.5683
{
  "data": {
    "hash": "u3butzxby",
    "precision": 9,
    "cell": {
      "south": 55.676093101501465,
      "west": 12.568273544311523,
      "north": 55.6761360168457,
      "east": 12.568316459655762
    },
    "center": {
      "lat": 55.676114559173584,
      "lng": 12.568295001983643
    },
    "error": {
      "latDegrees": 0.000021457672119140625,
      "lngDegrees": 0.000021457672119140625
    },
    "approxCellMetersAtEquator": {
      "width": 4.7773,
      "height": 4.7453
    }
  },
  "meta": {
    "endpoint": "geohash",
    "route": "/encode",
    "params": {
      "lat": 55.6761,
      "lng": 12.5683,
      "precision": 9
    },
    "generatedAt": "2026-07-30T15:14:08.000Z"
  }
}
Precision 5 reproduces the classic ezs42 cell
GET /api/geohash/encode?lat=42.6&lng=-5.6&precision=5
{
  "data": {
    "hash": "ezs42",
    "precision": 5,
    "cell": {
      "south": 42.5830078125,
      "west": -5.625,
      "north": 42.626953125,
      "east": -5.5810546875
    },
    "center": {
      "lat": 42.60498046875,
      "lng": -5.60302734375
    },
    "error": {
      "latDegrees": 0.02197265625,
      "lngDegrees": 0.02197265625
    },
    "approxCellMetersAtEquator": {
      "width": 4891.9698,
      "height": 4859.2211
    }
  },
  "meta": {
    "endpoint": "geohash",
    "route": "/encode",
    "params": {
      "lat": 42.6,
      "lng": -5.6,
      "precision": 5
    },
    "generatedAt": "2026-07-30T15:14:08.000Z"
  }
}
Eleven characters for a Jutland test point
GET /api/geohash/encode?lat=57.64911&lng=10.40744&precision=11
{
  "data": {
    "hash": "u4pruydqqvj",
    "precision": 11,
    "cell": {
      "south": 57.649109959602356,
      "west": 10.407439023256302,
      "north": 57.64911130070686,
      "east": 10.40744036436081
    },
    "center": {
      "lat": 57.64911063015461,
      "lng": 10.407439693808556
    },
    "error": {
      "latDegrees": 6.705522537231445e-7,
      "lngDegrees": 6.705522537231445e-7
    },
    "approxCellMetersAtEquator": {
      "width": 0.1493,
      "height": 0.1483
    }
  },
  "meta": {
    "endpoint": "geohash",
    "route": "/encode",
    "params": {
      "lat": 57.64911,
      "lng": 10.40744,
      "precision": 11
    },
    "generatedAt": "2026-07-30T15:14:08.000Z"
  }
}

GET /api/geohash/decode

Returns the rectangle the hash actually denotes — the bounds come from the bit ranges, so the cell is exact rather than reconstructed from a rounded point.

Live requestRuns against the public API
No key required
GET/api/geohash/decode?hash=ezs42

Request parameters

Geohash of 1–12 characters from the base-32 alphabet 0123456789bcdefghjkmnpqrstuvwxyz (a, i, l and o are not in it). Invalid characters are a 400, never corrected — including uppercase, a declared deviation from CTA-5009-A §8.1's SHOULD-lowercase.

Advanced response options4 options

Return only these fields (comma-separated). Mutually exclusive with 'exclude'.

Return all fields except these (comma-separated).

Pretty-print the JSON response.

Drop the envelope: return the raw array/object without data/meta wrapper.

Response

Example parameters are ready. Send the request to inspect the live response.

Route reference

Code samples Ready-to-copy requests in 4 languages
Choose a code sample language
curl "https://randomapi.dev/api/geohash/decode?hash=ezs42"
const res = await fetch("https://randomapi.dev/api/geohash/decode?hash=ezs42");
const { data, meta } = await res.json();
import requests

data = requests.get("https://randomapi.dev/api/geohash/decode?hash=ezs42").json()["data"]
$json = json_decode(file_get_contents(
  "https://randomapi.dev/api/geohash/decode?hash=ezs42"
), true);
$data = $json["data"];
Parameters Route-specific request inputs 1
hash string required

Geohash of 1–12 characters from the base-32 alphabet 0123456789bcdefghjkmnpqrstuvwxyz (a, i, l and o are not in it). Invalid characters are a 400, never corrected — including uppercase, a declared deviation from CTA-5009-A §8.1's SHOULD-lowercase.

allowed: 1 – 12
example: hash=ezs42
Universal parameters Shared response and formatting options 4
fields list

Return only these fields (comma-separated). Mutually exclusive with 'exclude'.

example: fields=hash,precision
exclude list

Return all fields except these (comma-separated).

example: exclude=parent
pretty boolean

Pretty-print the JSON response.

default: false
example: pretty=true
unwrap boolean

Drop the envelope: return the raw array/object without data/meta wrapper.

default: false
example: unwrap=true
Response schema Fields returned in each record 7
hash string

The geohash this cell belongs to, exactly as validated.

example: ezs42

precision integer (1-12)

Hash length in characters; each character adds 5 bits.

example: 5

cell object

Exact cell rectangle in decimal degrees: south, west, north, east. Bounds are binary fractions of the ±90/±180 ranges, so they are exact, not rounded.

example: {"south":42.5830078125,"west":-5.625,"north":42.626953125,"east":-5.5810546875}

center object

Midpoint of the cell as lat/lng — the best single-point estimate, not a stored coordinate.

example: {"lat":42.60498046875,"lng":-5.60302734375}

error object

Half the cell's height and width in degrees: the maximum error of using center as a point.

example: {"latDegrees":0.02197265625,"lngDegrees":0.02197265625}

approxCellMetersAtEquator object

Approximate cell width and height in meters AT THE EQUATOR (111319.5 m per degree of longitude, 110574.3 m per degree of latitude), rounded to 4 decimals. Width shrinks with the cosine of latitude elsewhere.

example: {"width":4891.9698,"height":4859.2211}

parent string nullable

The hash minus its last character — the containing cell one precision coarser. Null at precision 1.

example: ezs4

Documented examples

Build-generated requests and complete responses
3
Decode the classic ezs42 cell
GET /api/geohash/decode?hash=ezs42
{
  "data": {
    "hash": "ezs42",
    "precision": 5,
    "cell": {
      "south": 42.5830078125,
      "west": -5.625,
      "north": 42.626953125,
      "east": -5.5810546875
    },
    "center": {
      "lat": 42.60498046875,
      "lng": -5.60302734375
    },
    "error": {
      "latDegrees": 0.02197265625,
      "lngDegrees": 0.02197265625
    },
    "approxCellMetersAtEquator": {
      "width": 4891.9698,
      "height": 4859.2211
    },
    "parent": "ezs4"
  },
  "meta": {
    "endpoint": "geohash",
    "route": "/decode",
    "params": {
      "hash": "ezs42"
    },
    "generatedAt": "2026-07-30T15:14:08.000Z"
  }
}
Decode a precision-9 Copenhagen hash
GET /api/geohash/decode?hash=u3butzxby
{
  "data": {
    "hash": "u3butzxby",
    "precision": 9,
    "cell": {
      "south": 55.676093101501465,
      "west": 12.568273544311523,
      "north": 55.6761360168457,
      "east": 12.568316459655762
    },
    "center": {
      "lat": 55.676114559173584,
      "lng": 12.568295001983643
    },
    "error": {
      "latDegrees": 0.000021457672119140625,
      "lngDegrees": 0.000021457672119140625
    },
    "approxCellMetersAtEquator": {
      "width": 4.7773,
      "height": 4.7453
    },
    "parent": "u3butzxb"
  },
  "meta": {
    "endpoint": "geohash",
    "route": "/decode",
    "params": {
      "hash": "u3butzxby"
    },
    "generatedAt": "2026-07-30T15:14:08.000Z"
  }
}
A single character is a 45-degree world cell
GET /api/geohash/decode?hash=u
{
  "data": {
    "hash": "u",
    "precision": 1,
    "cell": {
      "south": 45,
      "west": 0,
      "north": 90,
      "east": 45
    },
    "center": {
      "lat": 67.5,
      "lng": 22.5
    },
    "error": {
      "latDegrees": 22.5,
      "lngDegrees": 22.5
    },
    "approxCellMetersAtEquator": {
      "width": 5009377.0857,
      "height": 4975842.412
    },
    "parent": null
  },
  "meta": {
    "endpoint": "geohash",
    "route": "/decode",
    "params": {
      "hash": "u"
    },
    "generatedAt": "2026-07-30T15:14:08.000Z"
  }
}

GET /api/geohash/neighbors

East and west wrap across the antimeridian. A direction that would leave the world is null and its reason is listed in 'unavailable' — never a substituted cell.

Live requestRuns against the public API
No key required
GET/api/geohash/neighbors?hash=ezs42

Request parameters

Geohash of 1–12 characters from the base-32 alphabet 0123456789bcdefghjkmnpqrstuvwxyz (a, i, l and o are not in it). Invalid characters are a 400, never corrected — including uppercase, a declared deviation from CTA-5009-A §8.1's SHOULD-lowercase.

Advanced response options4 options

Return only these fields (comma-separated). Mutually exclusive with 'exclude'.

Return all fields except these (comma-separated).

Pretty-print the JSON response.

Drop the envelope: return the raw array/object without data/meta wrapper.

Response

Example parameters are ready. Send the request to inspect the live response.

Route reference

Code samples Ready-to-copy requests in 4 languages
Choose a code sample language
curl "https://randomapi.dev/api/geohash/neighbors?hash=ezs42"
const res = await fetch("https://randomapi.dev/api/geohash/neighbors?hash=ezs42");
const { data, meta } = await res.json();
import requests

data = requests.get("https://randomapi.dev/api/geohash/neighbors?hash=ezs42").json()["data"]
$json = json_decode(file_get_contents(
  "https://randomapi.dev/api/geohash/neighbors?hash=ezs42"
), true);
$data = $json["data"];
Parameters Route-specific request inputs 1
hash string required

Geohash of 1–12 characters from the base-32 alphabet 0123456789bcdefghjkmnpqrstuvwxyz (a, i, l and o are not in it). Invalid characters are a 400, never corrected — including uppercase, a declared deviation from CTA-5009-A §8.1's SHOULD-lowercase.

allowed: 1 – 12
example: hash=ezs42
Universal parameters Shared response and formatting options 4
fields list

Return only these fields (comma-separated). Mutually exclusive with 'exclude'.

example: fields=hash,precision
exclude list

Return all fields except these (comma-separated).

example: exclude=unavailable
pretty boolean

Pretty-print the JSON response.

default: false
example: pretty=true
unwrap boolean

Drop the envelope: return the raw array/object without data/meta wrapper.

default: false
example: unwrap=true
Response schema Fields returned in each record 12
hash string

The geohash whose neighbors these are, exactly as validated.

example: ezs42

precision integer (1-12)

Hash length in characters; every neighbor has the same length.

example: 5

n string nullable

Cell immediately north; null when the cell's north edge is 90° N.

example: ezs48

ne string nullable

Cell to the north-east; null when the north edge is 90° N.

example: ezs49

e string

Cell immediately east, wrapping across the antimeridian. Never null — longitude always wraps.

example: ezs43

se string nullable

Cell to the south-east; null when the south edge is 90° S.

example: ezs41

s string nullable

Cell immediately south; null when the cell's south edge is 90° S.

example: ezs40

sw string nullable

Cell to the south-west; null when the south edge is 90° S.

example: ezefp

w string

Cell immediately west, wrapping across the antimeridian. Never null — longitude always wraps.

example: ezefr

nw string nullable

Cell to the north-west; null when the north edge is 90° N.

example: ezefx

wrapsAntimeridian boolean

True when the cell touches ±180°, so its eastern or western neighbors sit on the other side of the antimeridian.

example: false

unavailable object[]

One { direction, reason } entry for every null direction; empty when all eight neighbors exist.

example: []

Documented examples

Build-generated requests and complete responses
3
Eight neighbors of ezs42
GET /api/geohash/neighbors?hash=ezs42
{
  "data": {
    "hash": "ezs42",
    "precision": 5,
    "n": "ezs48",
    "ne": "ezs49",
    "e": "ezs43",
    "se": "ezs41",
    "s": "ezs40",
    "sw": "ezefp",
    "w": "ezefr",
    "nw": "ezefx",
    "wrapsAntimeridian": false,
    "unavailable": []
  },
  "meta": {
    "endpoint": "geohash",
    "route": "/neighbors",
    "params": {
      "hash": "ezs42"
    },
    "generatedAt": "2026-07-30T15:14:08.000Z"
  }
}
Antimeridian wrap and a polar edge at precision 1
GET /api/geohash/neighbors?hash=z
{
  "data": {
    "hash": "z",
    "precision": 1,
    "n": null,
    "ne": null,
    "e": "b",
    "se": "8",
    "s": "x",
    "sw": "w",
    "w": "y",
    "nw": null,
    "wrapsAntimeridian": true,
    "unavailable": [
      {
        "direction": "n",
        "reason": "the cell's northern edge is 90° N, so no geohash cell exists beyond it"
      },
      {
        "direction": "ne",
        "reason": "the cell's northern edge is 90° N, so no geohash cell exists beyond it"
      },
      {
        "direction": "nw",
        "reason": "the cell's northern edge is 90° N, so no geohash cell exists beyond it"
      }
    ]
  },
  "meta": {
    "endpoint": "geohash",
    "route": "/neighbors",
    "params": {
      "hash": "z"
    },
    "generatedAt": "2026-07-30T15:14:08.000Z"
  }
}
Neighbors of a precision-9 city cell
GET /api/geohash/neighbors?hash=u3butzxby
{
  "data": {
    "hash": "u3butzxby",
    "precision": 9,
    "n": "u3butzxcn",
    "ne": "u3butzxcp",
    "e": "u3butzxbz",
    "se": "u3butzxbx",
    "s": "u3butzxbw",
    "sw": "u3butzxbt",
    "w": "u3butzxbv",
    "nw": "u3butzxcj",
    "wrapsAntimeridian": false,
    "unavailable": []
  },
  "meta": {
    "endpoint": "geohash",
    "route": "/neighbors",
    "params": {
      "hash": "u3butzxby"
    },
    "generatedAt": "2026-07-30T15:14:08.000Z"
  }
}

About this API

Coverage & behavior

Geohash turns a latitude/longitude pair into a short base-32 string by interleaving the bits of the two coordinates — longitude first, alternating, five bits per character. Each character narrows the cell, so a shared prefix means a shared rectangle. The alphabet is 0123456789bcdefghjkmnpqrstuvwxyz: lowercase digits and letters with a, i, l and o left out.

A geohash is a rectangle, not a point. /decode therefore returns the cell's exact south/west/north/east bounds taken straight from the bit ranges, plus the center and the ± error — instead of a single reconstructed coordinate that hides how big the cell actually is. Every bound is a binary fraction of the ±90/±180 ranges, so at these precisions the numbers come back exact rather than rounded.

  • /encode takes lat, lng and an optional precision (default 9) and returns the hash together with its cell. lat=90 and lng=180 land in the northernmost and easternmost cells respectively — +180 is not normalized to -180, because those are opposite edges of the world.
  • /decode takes a hash and returns the same cell plus parent, the hash minus its last character (null at precision 1). A parent's cell always contains its child's.
  • /neighbors returns the eight adjacent cells n, ne, e, se, s, sw, w, nw. East/west wrap across the antimeridian: the cell west of b is z, and the cell east of z is b. Directions that would leave the world are null with a stated reason in unavailable — a cell touching 90° N has no northern neighbor, and we will not fabricate one.

Precision, cell size and the boundary problem

Precision Bits (lat + lng) ± lat ± lng Approx. cell at the equator
1 2 + 3 ±22.5° ±22.5° 5009.4 km × 4975.8 km
2 5 + 5 ±2.813° ±5.625° 1252.3 km × 622.0 km
3 7 + 8 ±0.7031° ±0.7031° 156.5 km × 155.5 km
4 10 + 10 ±0.08789° ±0.1758° 39.1 km × 19.4 km
5 12 + 13 ±0.02197° ±0.02197° 4.9 km × 4.9 km
6 15 + 15 ±0.002747° ±0.005493° 1.2 km × 607.4 m
7 17 + 18 ±0.0006866° ±0.0006866° 152.9 m × 151.9 m
8 20 + 20 ±0.00008583° ±0.0001717° 38.2 m × 19.0 m
9 22 + 23 ±0.00002146° ±0.00002146° 4.8 m × 4.7 m
10 25 + 25 ±0.000002682° ±0.000005364° 1.2 m × 59.3 cm
11 27 + 28 ±6.706e-7° ±6.706e-7° 14.9 cm × 14.8 cm
12 30 + 30 ±8.382e-8° ±1.676e-7° 3.7 cm × 1.9 cm

Meter figures use the WGS 84 equator, where one degree of longitude is 111319.5 m and one degree of latitude is 110574.3 m. They are approximations that hold only near the equator. Cells are degree rectangles: a cell keeps its height to within about 1% anywhere on Earth, but its width shrinks with the cosine of latitude — the precision-9 cell that is 4.8 m wide on the equator is about 2.7 m wide in Copenhagen. Odd precisions are square because longitude gets the extra bit; even precisions are twice as wide as they are tall.

The famous trap: two points a meter apart can have completely different geohashes when a cell boundary runs between them, so "find rows whose hash starts with X" silently misses neighbors. That is exactly why /neighbors exists — search the cell and its eight neighbors.

Standards, scope and declared deviations

  • Geohash is specified. Gustavo Niemeyer published the algorithm in 2008 and placed it in the public domain; it was later written up formally as CTA-5009-A, Fast and Readable Geographical Hashing (Consumer Technology Association, CTA WAVE Project — free of charge), the stable reference behind the IANA CBOR tag, JWT claim and CWT claim registrations for geohashes. This endpoint implements its §7 encoding and §8 decoding exactly, and the test suite pins the specification's own worked examples: 32.449247755342455°, -99.73357454336144° at length 9 → 9vc0de0nx and back to the same cell (§7.6/§8.5), the §9 cautionary pair 9zzzz/f0000, and the §10 Paris corners u09tgfr0w/u09wnmtwz whose longest common prefix is u09 — plus the community's canonical ezs42 and 57.64911,10.40744u4pruydqqvj.
  • Declared deviation — uppercase is rejected. CTA-5009-A §8.1 says a decoder should read upper case as lower case. This API deliberately does not: ?hash=EZS42 returns 400 that names the deviation and offers ezs42, because a silently repaired hash hides the bug that produced it. §7.4's MUST encode in lowercase is honored — every hash this API emits is lowercase. a, i, l and o are a different matter: they are not on the table at all, so refusing them is what the specification requires.
  • Declared extension — the range endpoints are inclusive. CTA-5009-A §7 makes the inputs half-open, latitude in [-90°, 90°) and longitude in [-180°, 180°). This API accepts the closed ranges instead: lat=90 lands in the northernmost cell and lng=180 in the easternmost, rather than 400-ing a legitimately measured pole or antimeridian reading. +180 is still never normalized to -180 — those are opposite edges of the world.
  • Precision stops at 12 (60 bits, about 3.7 cm × 1.9 cm at the equator). Longer values return 400 rather than implying accuracy nobody can supply.
  • This endpoint stores no points and answers no proximity queries. Use /api/geodesy for great-circle distance between the candidates a geohash search gives you, and /api/coordinate-formats to turn DMS, ISO 6709 or Maidenhead input into the decimal degrees /encode expects — that endpoint deliberately leaves geohash to this one.

Use it for

  • Bucket coordinates into fixed grid cells for heatmaps and map clustering without a geospatial library
  • Debug a prefix-based proximity query that misses points sitting just across a cell boundary
  • Build the nine-cell search set (a cell plus its eight neighbors) before a precise radius filter
  • Turn a stored geohash back into the exact rectangle so you can draw it on a map

Frequently asked questions

Why do two points a few meters apart have completely different geohashes?

Because a cell boundary runs between them. A geohash prefix only matches inside one cell, so prefix-based proximity search silently misses everything just across an edge — call /neighbors and search the cell plus its eight neighbors.

Is geohash defined by a standard?

Yes. The algorithm Gustavo Niemeyer published in 2008 was later specified as CTA-5009-A, Fast and Readable Geographical Hashing (CTA WAVE Project), which is free to download and is the stable reference for the IANA CBOR tag, JWT claim and CWT claim registrations for geohashes. This endpoint implements its §7 encoding and §8 decoding and reproduces the specification's own worked example — 32.449247755342455°, -99.73357454336144° at length 9 → 9vc0de0nx — with two deviations declared on this page: uppercase input returns 400 instead of being lower-cased per §8.1, and lat=90/lng=180 are accepted although §7 makes those ranges half-open.

How large is a geohash cell at each precision?

A precision-9 cell is about 4.8 m × 4.7 m at the equator; precision 5 is about 4.9 km × 4.9 km. Cells are degree rectangles, so those meter figures are approximate and hold only near the equator — the width shrinks with the cosine of latitude.

Why does the API reject a precision above 12?

Twelve characters is 60 bits — roughly a 3.7 cm × 1.9 cm cell, finer than any consumer positioning fix — and it is a common ceiling: Elasticsearch's geohash_grid rejects any precision outside 1–12. It is also this implementation's hard limit, because a 13th character needs 33 longitude bits and the bit packing uses 32-bit shifts, which top out at 30 bits per axis. Other stacks do go further — PostGIS's ST_GeoHash returns 20 characters for a point by default — so 12 is this API's documented boundary, not a universal one.

Which characters are valid in a geohash, and is uppercase accepted?

Only lowercase 0123456789bcdefghjkmnpqrstuvwxyz; a, i, l and o are not on CTA-5009-A's table, so the specification requires refusing them and this API returns 400. Uppercase is a deliberate deviation: §8.1 says a decoder should read EZS42 as ezs42, but this API returns 400 that names the deviation and suggests the lowercase form rather than silently repairing the input.

Can I search for points near a geohash with this API?

No. It converts between coordinates, cells and neighbors only — it indexes no points and runs no proximity queries. Pair it with /api/geodesy for great-circle distance once you have candidate cells.

Standards & references