---
title: "Fuzzy port-name search"
method: GET
path: "/api/v1/ports/search"
tags: ["grandline"]
---

# Fuzzy port-name search

`GET /api/v1/ports/search`

## Request

- Base URL: `https://grandline.luzomi.com`
- URL: `https://grandline.luzomi.com/api/v1/ports/search`
- Auth: none declared

Fuzzy search used by the autocomplete input on each side of the
distance calculator.

Matching strategy: case-insensitive prefix match first (`ILIKE 'q%'`),
then trigram similarity for misspellings. Results are ordered by a
composite score (prefix matches always rank above pure trigram hits)
so typing 'hou' surfaces 'Houston' ahead of 'South-East-Houthuizen'.

Two modes:

- **From side (no `from_port_id`)** — returns every name match, routable or
  not, each annotated with `routable`. A non-routable hit (e.g. Mumbai,
  which has polygons but zero cache rows because the AIS traffic lands at
  JNPT next door) carries `nearest_routable`: the closest port by
  great-circle that *is* routable, so the UI can offer "try JNPT instead"
  rather than letting the famous port silently disappear.
- **To side (`from_port_id` set)** — narrows to destinations actually
  reachable from that origin, so the user never sees a suggestion that
  would `route_not_in_cache` on click. These are all routable by
  construction (`routable=true`, `nearest_routable=null`).

## Query parameters

- `q` string, required: Partial port name. ILIKE prefix + trigram fallback.
- `from_port_id` integer, nullable: Optional — restrict to ports reachable from this origin (To side).
- `limit` integer

## Response `200`

Successful Response

- PortSearchHit[]
  - `port_id` integer, required
  - `port_name` string, required
  - `country_iso2` string, required
  - `routable` boolean
  - `nearest_routable` PortRow
    - `port_id` integer, required
    - `port_name` string, required
    - `country_iso2` string, required
  - `lat` number, nullable
  - `lon` number, nullable
  - `matched_alias` string, nullable
  - `canonical_name` string, nullable

## Other responses

- `400`: Validation error with extra context
- `404`: Resource not found
- `422`: Validation Error
- `429`: Rate-limit exceeded — see X-RateLimit-* headers

---

[API](https://skmtc.dev/luzomi/apis/grandline-api.md) · [All operations](https://skmtc.dev/luzomi/apis/grandline-api/llms.txt) · [OpenAPI document](https://skmtc.dev/luzomi/apis/grandline-api/revisions/4ce06aea012b?raw)
