← back to Cli Printing Press
feat(catalog): add OpenRouteService under new maps category (#1513)
43c90ba03553c41837c6959c13d99aafb41d1c8e · 2026-05-16 18:11:46 +0100 · liam-ai-reality
* feat(catalog): add OpenRouteService under new maps category
Adds OpenRouteService (ORS) to the embedded catalog under a new `maps`
category, the first map / routing / VRP entry. Backed by a hand-crafted
OpenAPI 3.0 spec covering directions, matrix, isochrones, optimization,
geocode forward/reverse.
- New `maps` category registered in internal/catalog/catalog.go,
internal/catalog/catalog_test.go, and the AGENTS.md prose enumeration.
- catalog/openrouteservice.yaml (tier: community, spec_source: docs).
- catalog/specs/openrouteservice-spec.yaml — hand-crafted OpenAPI 3.0.3,
11 schemas, 3 reusable responses, 9 routing profiles incl driving-hgv.
- Golden update for catalog-list/stdout.txt (+3 lines: maps block).
Refs #1512
Verified locally:
go test ./internal/catalog/... # 71 pass
go build ./... # clean
go vet ./... # clean
./scripts/golden.sh verify # 20 / 20 pass
* fix(cli): clarify openrouteservice auth notes
* chore(catalog): drop sandbox_endpoint and OPENROUTESERVICE_API_KEY alias
- sandbox_endpoint == prod for ORS (no separate test endpoint). Removed.
- ORS_API_KEY is the canonical env var used by ors-py / ors-js; alias
OPENROUTESERVICE_API_KEY is verbose and not used by any existing
community tool. Removed.
Refs #1512
* Update catalog/specs/openrouteservice-spec.yaml
Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
* Update catalog/specs/openrouteservice-spec.yaml
Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
* Update catalog/specs/openrouteservice-spec.yaml
Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
---------
Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
Files touched
M AGENTS.mdA catalog/openrouteservice.yamlA catalog/specs/openrouteservice-spec.yamlM internal/catalog/catalog.goM internal/catalog/catalog_test.goM testdata/golden/expected/catalog-list/stdout.txt
Diff
commit 43c90ba03553c41837c6959c13d99aafb41d1c8e
Author: liam-ai-reality <lm@aireality.io>
Date: Sat May 16 18:11:46 2026 +0100
feat(catalog): add OpenRouteService under new maps category (#1513)
* feat(catalog): add OpenRouteService under new maps category
Adds OpenRouteService (ORS) to the embedded catalog under a new `maps`
category, the first map / routing / VRP entry. Backed by a hand-crafted
OpenAPI 3.0 spec covering directions, matrix, isochrones, optimization,
geocode forward/reverse.
- New `maps` category registered in internal/catalog/catalog.go,
internal/catalog/catalog_test.go, and the AGENTS.md prose enumeration.
- catalog/openrouteservice.yaml (tier: community, spec_source: docs).
- catalog/specs/openrouteservice-spec.yaml — hand-crafted OpenAPI 3.0.3,
11 schemas, 3 reusable responses, 9 routing profiles incl driving-hgv.
- Golden update for catalog-list/stdout.txt (+3 lines: maps block).
Refs #1512
Verified locally:
go test ./internal/catalog/... # 71 pass
go build ./... # clean
go vet ./... # clean
./scripts/golden.sh verify # 20 / 20 pass
* fix(cli): clarify openrouteservice auth notes
* chore(catalog): drop sandbox_endpoint and OPENROUTESERVICE_API_KEY alias
- sandbox_endpoint == prod for ORS (no separate test endpoint). Removed.
- ORS_API_KEY is the canonical env var used by ors-py / ors-js; alias
OPENROUTESERVICE_API_KEY is verbose and not used by any existing
community tool. Removed.
Refs #1512
* Update catalog/specs/openrouteservice-spec.yaml
Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
* Update catalog/specs/openrouteservice-spec.yaml
Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
* Update catalog/specs/openrouteservice-spec.yaml
Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
---------
Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
---
AGENTS.md | 2 +-
catalog/openrouteservice.yaml | 50 ++
catalog/specs/openrouteservice-spec.yaml | 689 +++++++++++++++++++++++
internal/catalog/catalog.go | 1 +
internal/catalog/catalog_test.go | 2 +-
testdata/golden/expected/catalog-list/stdout.txt | 3 +
6 files changed, 745 insertions(+), 2 deletions(-)
diff --git a/AGENTS.md b/AGENTS.md
index 8f2ea645..723ea5a2 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -176,7 +176,7 @@ When adding or editing `catalog/*.yaml`, first decide whether the entry belongs
- The entry must pass `internal/catalog` validation.
- Required fields: `name`, `display_name`, `description`, `category`, and `tier`, plus `spec_url` and `spec_format` unless the entry is wrapper-only (`wrapper_libraries` is set and `spec_url` is omitted).
- `spec_url`, when present, must use HTTPS.
-- `category` must be one of `ai`, `auth`, `cloud`, `commerce`, `developer-tools`, `devices`, `food-and-dining`, `marketing`, `media-and-entertainment`, `monitoring`, `payments`, `productivity`, `project-management`, `sales-and-crm`, `social-and-messaging`, `travel`, or `other`. The validator also accepts `example` as a test-only catch-all; do not use it for real catalog entries.
+- `category` must be one of `ai`, `auth`, `cloud`, `commerce`, `developer-tools`, `devices`, `food-and-dining`, `maps`, `marketing`, `media-and-entertainment`, `monitoring`, `payments`, `productivity`, `project-management`, `sales-and-crm`, `social-and-messaging`, `travel`, or `other`. The validator also accepts `example` as a test-only catch-all; do not use it for real catalog entries.
- `tier` must be `official` or `community`.
- `bearer_refresh`, when present, must include `bundle_url` and `pattern`; `bundle_url` must use HTTPS, and `pattern` must compile as a Go regexp.
- `auth_key_url`, when present, must use HTTPS. It overrides any URL inferred from the spec and surfaces in the printed CLI as `Get a key at: <URL>`.
diff --git a/catalog/openrouteservice.yaml b/catalog/openrouteservice.yaml
new file mode 100644
index 00000000..c4ab17f9
--- /dev/null
+++ b/catalog/openrouteservice.yaml
@@ -0,0 +1,50 @@
+name: openrouteservice
+display_name: OpenRouteService
+description: Open-source routing, geocoding, matrix, isochrones, and VRP optimization on OpenStreetMap data.
+category: maps
+spec_url: https://raw.githubusercontent.com/mvanhorn/cli-printing-press/main/catalog/specs/openrouteservice-spec.yaml
+spec_format: yaml
+openapi_version: "3.0"
+tier: community
+spec_source: docs
+verified_date: "2026-05-16"
+homepage: https://openrouteservice.org
+auth_key_url: https://openrouteservice.org/dev/#/signup
+auth_required: true
+auth_instructions: Free signup at https://openrouteservice.org/dev/#/signup. Pass the key as the raw Authorization header value (no Bearer prefix).
+auth_env_vars:
+ - ORS_API_KEY
+notes: |
+ Single vendor covers everything a backend dispatch / routing workflow needs:
+
+ - geocode/search, geocode/reverse (Pelias) — address ↔ coordinate
+ - directions/{profile} — single route + ETA + geometry
+ - matrix/{profile} — N×M duration / distance matrix; the primary input for
+ VRP solvers like or-tools VRPTW
+ - isochrones/{profile} — reachable-area polygons; service-zone definitions
+ - /optimization (Vroom) — built-in VRP solver; alternative to running
+ or-tools locally for small (≤50 jobs, ≤3 vehicles) problems
+
+ Hosted free-plan limits: 2,000 directions/day, 500 matrix/day,
+ 500 optimization/day, 500 isochrones/day, 1,000 geocode/day, 40/min on
+ most endpoints. Self-hosted ORS Docker removes request caps for routing,
+ matrix, isochrones, and optimization; geocoding is hosted-public-API only
+ and is not available on a stock self-hosted ORS instance.
+
+ Profiles include driving-car, driving-hgv (service vans / utility vehicles —
+ respects bridge weight, low-clearance, large-vehicle turn restrictions),
+ cycling-* (regular / mountain / road / electric), foot-* (walking / hiking),
+ and wheelchair.
+
+ Spec sourcing: the spec checked in at catalog/specs/openrouteservice-spec.yaml
+ is hand-crafted from the public API reference at
+ https://giscience.github.io/openrouteservice/api-reference/ and covers the
+ high-leverage endpoints listed above. ORS itself generates a SpringDoc spec
+ at runtime — for the full surface (POIs, snap-to-road, elevation, advanced
+ optimization constraints), run `docker run -p 8080:8080
+ openrouteservice/openrouteservice:latest` and fetch
+ `http://localhost:8080/ors/v2/api-docs` to refresh this spec, then re-print
+ via /printing-press-reprint openrouteservice.
+
+ The same printed binary can target hosted ORS or a self-hosted ORS backend
+ via --base-url for routing, matrix, isochrones, and optimization endpoints.
diff --git a/catalog/specs/openrouteservice-spec.yaml b/catalog/specs/openrouteservice-spec.yaml
new file mode 100644
index 00000000..18ff30c2
--- /dev/null
+++ b/catalog/specs/openrouteservice-spec.yaml
@@ -0,0 +1,689 @@
+openapi: "3.0.3"
+info:
+ title: OpenRouteService API
+ description: |
+ Open-source routing, geocoding, isochrone, distance-matrix, and VRP optimization API
+ built on OpenStreetMap. Pelias powers geocoding; Vroom powers optimization.
+
+ This spec covers the endpoints commonly used for back-end agent workflows
+ (geocoding, matrices for VRP solvers, route ETAs, isochrones, full VRP solve).
+ The full ORS surface (POIs, snap-to-road, elevation, optimization advanced
+ constraints) can be added incrementally — see notes in the catalog entry.
+
+ The hosted free API (`https://api.openrouteservice.org`) exposes routing,
+ matrix, isochrones, optimization, and geocoding. Self-hosted ORS Docker
+ covers the ORS backend endpoints (routing, matrix, isochrones, optimization);
+ geocoding is hosted-public-API only.
+
+ Auth: pass the API key in the `Authorization` header. Sign up free at
+ https://openrouteservice.org/dev/#/signup.
+
+ Rate limits (free plan, hosted): 2,000 directions/day, 500 matrix/day,
+ 500 optimization/day, 500 isochrones/day, 1,000 geocode/day, 40/min on
+ most endpoints. Self-hosted backend endpoints: no hosted request caps.
+ version: "v2"
+ contact:
+ name: OpenRouteService
+ url: https://openrouteservice.org
+
+servers:
+ - url: https://api.openrouteservice.org
+ description: Hosted free API (requires API key)
+
+security:
+ - apiKey: []
+
+tags:
+ - name: directions
+ description: Route between two or more points with ETA, distance, and geometry.
+ - name: matrix
+ description: Many-to-many duration / distance matrix. Primary input for VRP solvers.
+ - name: isochrones
+ description: Reachable-area polygons by time or distance.
+ - name: optimization
+ description: VRP solver (Vroom backend). Assign N jobs to M vehicles optimally.
+ - name: geocode
+ description: Pelias forward and reverse geocoding.
+
+paths:
+ /v2/directions/{profile}:
+ post:
+ operationId: getDirections
+ summary: Compute a route between coordinates
+ description: |
+ Returns the ordered route, total duration, total distance, and turn-by-turn
+ steps. Use `driving-hgv` for service vans / heavy-goods routing (better
+ bridge-weight and turn-restriction handling than `driving-car`).
+ tags: [directions]
+ parameters:
+ - $ref: '#/components/parameters/Profile'
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/DirectionsRequest'
+ responses:
+ '200':
+ description: Route computed
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/DirectionsResponse'
+ '400': { $ref: '#/components/responses/BadRequest' }
+ '401': { $ref: '#/components/responses/Unauthorized' }
+ '429': { $ref: '#/components/responses/RateLimited' }
+
+ /v2/matrix/{profile}:
+ post:
+ operationId: getMatrix
+ summary: Many-to-many duration / distance matrix
+ description: |
+ Returns an N×M matrix of durations (seconds) and/or distances (metres) between
+ all origin / destination pairs. This is the primary input for a Vehicle Routing
+ Problem solver — feed the matrix into or-tools VRPTW.
+
+ Free-plan limits: 3,500 locations per request total (origins × destinations),
+ or 25 with `metrics: ['duration', 'distance']`.
+ tags: [matrix]
+ parameters:
+ - $ref: '#/components/parameters/Profile'
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/MatrixRequest'
+ responses:
+ '200':
+ description: Matrix computed
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/MatrixResponse'
+ '400': { $ref: '#/components/responses/BadRequest' }
+ '401': { $ref: '#/components/responses/Unauthorized' }
+ '429': { $ref: '#/components/responses/RateLimited' }
+
+ /v2/isochrones/{profile}:
+ post:
+ operationId: getIsochrones
+ summary: Reachable-area polygons by time or distance
+ description: |
+ Returns one polygon per requested location × range — the area reachable
+ within the given travel time (seconds) or distance (metres). Useful for
+ defining service zones, dispatcher catchment analysis, and SLA-feasibility
+ checks at a glance.
+ tags: [isochrones]
+ parameters:
+ - $ref: '#/components/parameters/Profile'
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/IsochronesRequest'
+ responses:
+ '200':
+ description: Isochrones computed
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/IsochronesResponse'
+ '400': { $ref: '#/components/responses/BadRequest' }
+ '401': { $ref: '#/components/responses/Unauthorized' }
+ '429': { $ref: '#/components/responses/RateLimited' }
+
+ /optimization:
+ post:
+ operationId: solveOptimization
+ summary: Solve a Vehicle Routing Problem (Vroom)
+ description: |
+ Assigns jobs to vehicles optimally, minimising total travel time subject
+ to time windows, skill matching, capacity, and breaks. Backed by Vroom.
+
+ Free-plan limits: 50 jobs, 3 vehicles per request. Self-hosted ORS has no
+ such caps. For larger problems, run or-tools VRPTW locally and use only
+ the `/v2/matrix/*` endpoint here.
+ tags: [optimization]
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/OptimizationRequest'
+ responses:
+ '200':
+ description: Optimised plan
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/OptimizationResponse'
+ '400': { $ref: '#/components/responses/BadRequest' }
+ '401': { $ref: '#/components/responses/Unauthorized' }
+ '429': { $ref: '#/components/responses/RateLimited' }
+
+ /geocode/search:
+ get:
+ operationId: geocodeSearch
+ summary: Forward geocode an address or place name
+ description: |
+ Pelias forward geocoder. Converts a free-text address into one or more
+ candidate coordinates with provenance, confidence, and structured address
+ components. Restrict to a country with `boundary.country` (ISO 3166-1
+ alpha-2 or alpha-3) for higher-quality matches.
+ tags: [geocode]
+ parameters:
+ - in: query
+ name: text
+ required: true
+ schema: { type: string }
+ description: Free-form address or place name.
+ - in: query
+ name: focus.point.lon
+ schema: { type: number, format: double }
+ description: Longitude to bias results toward.
+ - in: query
+ name: focus.point.lat
+ schema: { type: number, format: double }
+ description: Latitude to bias results toward.
+ - in: query
+ name: boundary.country
+ schema: { type: string }
+ description: ISO 3166-1 country code to restrict results to (e.g. `AU`).
+ - in: query
+ name: size
+ schema: { type: integer, default: 10, minimum: 1, maximum: 40 }
+ description: Maximum candidates to return.
+ responses:
+ '200':
+ description: Geocode candidates
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/GeocodeResponse'
+ '400': { $ref: '#/components/responses/BadRequest' }
+ '401': { $ref: '#/components/responses/Unauthorized' }
+ '429': { $ref: '#/components/responses/RateLimited' }
+
+ /geocode/reverse:
+ get:
+ operationId: geocodeReverse
+ summary: Reverse-geocode a coordinate into an address
+ description: |
+ Returns the nearest known address(es) for a given coordinate. Pelias-backed.
+ tags: [geocode]
+ parameters:
+ - in: query
+ name: point.lon
+ required: true
+ schema: { type: number, format: double }
+ description: Longitude.
+ - in: query
+ name: point.lat
+ required: true
+ schema: { type: number, format: double }
+ description: Latitude.
+ - in: query
+ name: size
+ schema: { type: integer, default: 10, minimum: 1, maximum: 40 }
+ description: Maximum candidates to return.
+ - in: query
+ name: boundary.circle.radius
+ schema: { type: number, format: double }
+ description: Restrict to this radius (km) around the point.
+ responses:
+ '200':
+ description: Reverse-geocode candidates
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/GeocodeResponse'
+ '400': { $ref: '#/components/responses/BadRequest' }
+ '401': { $ref: '#/components/responses/Unauthorized' }
+ '429': { $ref: '#/components/responses/RateLimited' }
+
+components:
+ securitySchemes:
+ apiKey:
+ type: apiKey
+ in: header
+ name: Authorization
+ description: |
+ Free key from https://openrouteservice.org/dev/#/signup. Pass the raw key
+ as the `Authorization` header value (no `Bearer ` prefix). The CLI reads
+ the key from the `ORS_API_KEY` environment variable.
+
+ parameters:
+ Profile:
+ in: path
+ name: profile
+ required: true
+ schema:
+ type: string
+ enum:
+ - driving-car
+ - driving-hgv
+ - cycling-regular
+ - cycling-mountain
+ - cycling-road
+ - cycling-electric
+ - foot-walking
+ - foot-hiking
+ - wheelchair
+ description: |
+ Routing profile. `driving-hgv` is the right pick for service vans / utility
+ vehicles — it respects bridge weight, low-clearance, and large-vehicle turn
+ restrictions that `driving-car` ignores.
+
+ schemas:
+ Coordinate:
+ type: array
+ description: '[lon, lat] pair, decimal degrees, WGS84.'
+ items: { type: number, format: double }
+ minItems: 2
+ maxItems: 2
+
+ DirectionsRequest:
+ type: object
+ required: [coordinates]
+ properties:
+ coordinates:
+ type: array
+ minItems: 2
+ maxItems: 50
+ items: { $ref: '#/components/schemas/Coordinate' }
+ description: Ordered route waypoints. First is origin, last is destination.
+ alternative_routes:
+ type: object
+ properties:
+ target_count: { type: integer, minimum: 1, maximum: 3 }
+ weight_factor: { type: number, format: double }
+ share_factor: { type: number, format: double }
+ units:
+ type: string
+ enum: [m, km, mi]
+ default: m
+ language:
+ type: string
+ description: Localisation for textual turn instructions (e.g. `en-AU`).
+ instructions:
+ type: boolean
+ default: true
+ elevation:
+ type: boolean
+ default: false
+ preference:
+ type: string
+ enum: [fastest, shortest, recommended]
+ default: fastest
+ radiuses:
+ type: array
+ items: { type: number }
+ description: Per-waypoint snap-radius (m). Use -1 to disable snapping for that point.
+
+ DirectionsResponse:
+ type: object
+ properties:
+ routes:
+ type: array
+ items:
+ type: object
+ properties:
+ summary:
+ type: object
+ properties:
+ distance: { type: number, format: double, description: 'Total distance, units depend on `units` request field.' }
+ duration: { type: number, format: double, description: 'Total duration in seconds.' }
+ geometry:
+ type: string
+ description: Polyline-encoded geometry (precision 5).
+ segments:
+ type: array
+ items:
+ type: object
+ properties:
+ distance: { type: number, format: double }
+ duration: { type: number, format: double }
+ steps:
+ type: array
+ items: { type: object, additionalProperties: true }
+ bbox:
+ type: array
+ items: { type: number, format: double }
+ way_points:
+ type: array
+ items: { type: integer }
+ bbox:
+ type: array
+ items: { type: number, format: double }
+ metadata:
+ type: object
+ additionalProperties: true
+
+ MatrixRequest:
+ type: object
+ required: [locations]
+ properties:
+ locations:
+ type: array
+ minItems: 2
+ items: { $ref: '#/components/schemas/Coordinate' }
+ description: All locations participating in the matrix.
+ sources:
+ type: array
+ items: { type: integer }
+ description: Indices into `locations` to use as origins. Omit to use all locations.
+ destinations:
+ type: array
+ items: { type: integer }
+ description: Indices into `locations` to use as destinations. Omit to use all locations.
+ metrics:
+ type: array
+ items: { type: string, enum: [duration, distance] }
+ default: [duration]
+ units:
+ type: string
+ enum: [m, km, mi]
+ default: m
+ resolve_locations:
+ type: boolean
+ default: false
+
+ MatrixResponse:
+ type: object
+ properties:
+ durations:
+ type: array
+ description: N×M matrix of durations in seconds.
+ items:
+ type: array
+ items: { type: number, format: double, nullable: true }
+ distances:
+ type: array
+ description: N×M matrix of distances (units per request).
+ items:
+ type: array
+ items: { type: number, format: double, nullable: true }
+ sources:
+ type: array
+ items: { type: object, additionalProperties: true }
+ destinations:
+ type: array
+ items: { type: object, additionalProperties: true }
+ metadata:
+ type: object
+ additionalProperties: true
+
+ IsochronesRequest:
+ type: object
+ required: [locations, range]
+ properties:
+ locations:
+ type: array
+ minItems: 1
+ maxItems: 5
+ items: { $ref: '#/components/schemas/Coordinate' }
+ range:
+ type: array
+ items: { type: number, format: double }
+ description: Time (seconds) or distance (metres) thresholds. One polygon per value, per location.
+ range_type:
+ type: string
+ enum: [time, distance]
+ default: time
+ interval:
+ type: number
+ format: double
+ description: Optional sub-interval to densify the response.
+ attributes:
+ type: array
+ items: { type: string, enum: [area, reachfactor, total_pop] }
+ smoothing:
+ type: number
+ format: double
+ minimum: 0
+ maximum: 100
+ default: 0
+
+ IsochronesResponse:
+ type: object
+ properties:
+ type: { type: string }
+ bbox:
+ type: array
+ items: { type: number, format: double }
+ features:
+ type: array
+ items:
+ type: object
+ properties:
+ type: { type: string }
+ properties:
+ type: object
+ additionalProperties: true
+ geometry:
+ type: object
+ additionalProperties: true
+ metadata:
+ type: object
+ additionalProperties: true
+
+ OptimizationRequest:
+ type: object
+ required: [jobs, vehicles]
+ properties:
+ jobs:
+ type: array
+ items:
+ type: object
+ required: [id, location]
+ properties:
+ id: { type: integer }
+ location:
+ type: array
+ items: { type: number, format: double }
+ minItems: 2
+ maxItems: 2
+ service: { type: integer, description: 'On-site service duration in seconds.' }
+ amount:
+ type: array
+ items: { type: integer }
+ description: Capacity demand vector (matches vehicle.capacity dimensions).
+ skills:
+ type: array
+ items: { type: integer }
+ time_windows:
+ type: array
+ items:
+ type: array
+ items: { type: integer, description: 'Unix epoch seconds.' }
+ minItems: 2
+ maxItems: 2
+ priority: { type: integer, minimum: 0, maximum: 100 }
+ vehicles:
+ type: array
+ items:
+ type: object
+ required: [id, profile]
+ properties:
+ id: { type: integer }
+ profile:
+ type: string
+ enum:
+ - driving-car
+ - driving-hgv
+ - cycling-regular
+ - cycling-mountain
+ - cycling-road
+ - cycling-electric
+ - foot-walking
+ - foot-hiking
+ - wheelchair
+ start:
+ type: array
+ items: { type: number, format: double }
+ minItems: 2
+ maxItems: 2
+ end:
+ type: array
+ items: { type: number, format: double }
+ minItems: 2
+ maxItems: 2
+ capacity:
+ type: array
+ items: { type: integer }
+ skills:
+ type: array
+ items: { type: integer }
+ time_window:
+ type: array
+ items: { type: integer }
+ minItems: 2
+ maxItems: 2
+ breaks:
+ type: array
+ items:
+ type: object
+ properties:
+ id: { type: integer }
+ service: { type: integer }
+ time_windows:
+ type: array
+ items:
+ type: array
+ items: { type: integer }
+ minItems: 2
+ maxItems: 2
+ options:
+ type: object
+ properties:
+ g: { type: boolean, description: 'Include route geometries in response.' }
+
+ OptimizationResponse:
+ type: object
+ properties:
+ code: { type: integer, description: '0 = success.' }
+ summary:
+ type: object
+ properties:
+ cost: { type: integer }
+ duration: { type: integer }
+ distance: { type: integer }
+ unassigned: { type: integer }
+ service: { type: integer }
+ waiting_time: { type: integer }
+ routes:
+ type: array
+ items:
+ type: object
+ properties:
+ vehicle: { type: integer }
+ steps:
+ type: array
+ items:
+ type: object
+ properties:
+ type: { type: string, enum: [start, job, break, end] }
+ location:
+ type: array
+ items: { type: number, format: double }
+ job: { type: integer }
+ service: { type: integer }
+ waiting_time: { type: integer }
+ arrival: { type: integer }
+ duration: { type: integer }
+ cost: { type: integer }
+ duration: { type: integer }
+ distance: { type: integer }
+ service: { type: integer }
+ waiting_time: { type: integer }
+ geometry: { type: string }
+ unassigned:
+ type: array
+ items:
+ type: object
+ properties:
+ id: { type: integer }
+ location:
+ type: array
+ items: { type: number, format: double }
+ reason: { type: string }
+
+ GeocodeResponse:
+ type: object
+ properties:
+ type: { type: string, enum: [FeatureCollection] }
+ bbox:
+ type: array
+ items: { type: number, format: double }
+ features:
+ type: array
+ items:
+ type: object
+ properties:
+ type: { type: string }
+ geometry:
+ type: object
+ properties:
+ type: { type: string, enum: [Point] }
+ coordinates:
+ type: array
+ items: { type: number, format: double }
+ minItems: 2
+ maxItems: 2
+ properties:
+ type: object
+ properties:
+ id: { type: string }
+ gid: { type: string }
+ layer: { type: string }
+ source: { type: string }
+ name: { type: string }
+ confidence: { type: number, format: double }
+ accuracy: { type: string }
+ country: { type: string }
+ country_a: { type: string, description: 'ISO 3166 alpha-3.' }
+ region: { type: string }
+ locality: { type: string }
+ street: { type: string }
+ housenumber: { type: string }
+ postalcode: { type: string }
+ label: { type: string, description: 'Human-readable full address.' }
+ geocoding:
+ type: object
+ additionalProperties: true
+
+ ApiError:
+ type: object
+ properties:
+ error:
+ type: object
+ properties:
+ code: { type: integer }
+ message: { type: string }
+ info:
+ type: object
+ properties:
+ engine:
+ type: object
+ additionalProperties: true
+ attribution: { type: string }
+ timestamp: { type: integer }
+
+ responses:
+ BadRequest:
+ description: Malformed request
+ content:
+ application/json:
+ schema: { $ref: '#/components/schemas/ApiError' }
+ Unauthorized:
+ description: API key missing or invalid
+ content:
+ application/json:
+ schema: { $ref: '#/components/schemas/ApiError' }
+ RateLimited:
+ description: Per-minute or daily quota exhausted
+ content:
+ application/json:
+ schema: { $ref: '#/components/schemas/ApiError' }
diff --git a/internal/catalog/catalog.go b/internal/catalog/catalog.go
index fe3daeb2..f7cfc8b5 100644
--- a/internal/catalog/catalog.go
+++ b/internal/catalog/catalog.go
@@ -31,6 +31,7 @@ var validCategories = map[string]struct{}{
"developer-tools": {},
"devices": {},
"food-and-dining": {},
+ "maps": {},
"marketing": {},
"media-and-entertainment": {},
"monitoring": {},
diff --git a/internal/catalog/catalog_test.go b/internal/catalog/catalog_test.go
index b23f8082..227c2bfc 100644
--- a/internal/catalog/catalog_test.go
+++ b/internal/catalog/catalog_test.go
@@ -242,7 +242,7 @@ func TestValidateEntry(t *testing.T) {
func TestAllPublicCategoriesAreValid(t *testing.T) {
publicCategories := []string{
"ai", "auth", "cloud", "commerce", "developer-tools", "devices",
- "food-and-dining", "marketing", "media-and-entertainment", "monitoring",
+ "food-and-dining", "maps", "marketing", "media-and-entertainment", "monitoring",
"payments", "productivity", "project-management", "sales-and-crm",
"social-and-messaging", "travel", "other",
}
diff --git a/testdata/golden/expected/catalog-list/stdout.txt b/testdata/golden/expected/catalog-list/stdout.txt
index 398e4f31..27baa4da 100644
--- a/testdata/golden/expected/catalog-list/stdout.txt
+++ b/testdata/golden/expected/catalog-list/stdout.txt
@@ -16,6 +16,9 @@ developer-tools:
example:
petstore Canonical OpenAPI example - pet store management
+maps:
+ openrouteservice Open-source routing, geocoding, matrix, isochrones, and VRP optimization on OpenStreetMap data.
+
marketing:
producthunt Find, monitor, and export Product Hunt launches for launch research, scouting, and competitive tracking without requiring a Product Hunt API application.
← bf9014d4 fix(cli): synthesize undeclared path placeholders in OpenAPI
·
back to Cli Printing Press
·
fix(cli): match both singular and plural required-flag cobra f5ea270d →