Skip to content

Commit 9bf5628

Browse files
Merge places get/batch into one command, always using the batch endpoint
mapbox places <mapbox-id>... replaces places get/places batch: reviewed as the wrong shape for any API with a single-item GET and a batch POST of the same thing. A caller should not have to choose between one id and a hand-typed JSON array. The GET operation is gone from the spec entirely, not hidden; batch is the one that survives, flattened to the bare `places` command (new `FLATTENED_SERVICES`/`declared_command` mechanism, ported into this branch since the directions/isochrone/map-match/matrix stack that introduces it hasn't merged yet), and always sends the batch request, one id or many. New general mechanism, VARIADIC_BODY_ARRAY in spec.rs: a JSON body's array field exposed as one or more positional arguments collected into it, in place of --data, with its description read off the same schema `properties.<field>.description` body_field_parameters already reads for BODY_FIELD_FLAGS. clap's num_args(1..=max) enforces the 100-id limit client-side, before the request goes out. A 206 partial result (some ids unresolved) needed no new handling: status.is_success() already treats any 2xx as success. Fixed the -o text documentation while rewriting this page: the old places batch example showed raw JSON for -o text, but {"results": [...]} is a one-key object wrapping an array, which render_human's wrapped_list already turns into a table — verified by actually running the real rendering code against the page's own previously-captured production data, not guessed. Nested fields (coordinates, address, score) and arrays (categories, photos) are dropped from table columns, same rule every table on this page follows; documented that explicitly since the old page didn't show a wide enough record to make it obvious. Also reworded the CHANGELOG entry to drop process language ("this session's other additions", "discovered while writing") in favor of product facts, and added that Places is Public Preview with a 1000-records-per-account monthly quota. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
1 parent e697465 commit 9bf5628

10 files changed

Lines changed: 512 additions & 231 deletions

File tree

‎README.md‎

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -244,14 +244,18 @@ mapbox accounts <operation>
244244
mapbox feedback <operation>
245245
mapbox fonts <operation>
246246
mapbox geocoder <operation>
247-
mapbox places <operation>
248247
mapbox search <operation>
249248
mapbox sprites <operation>
250249
mapbox static <operation>
251250
mapbox styles <operation>
252251
mapbox tilesets <operation>
253252
```
254253

254+
`mapbox places` is the one exception: its API has a single operation, so
255+
there's a bare command with no subcommand at all, the same shape
256+
`mapbox usage` already has, see [docs/commands.md](./docs/commands.md)
257+
for its own parameters.
258+
255259
For example:
256260

257261
```sh

‎custom-openapi/places/openapi/places.yaml‎

Lines changed: 51 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -7,69 +7,41 @@ openapi: "3.0.0"
77
# Hand-authored down to the parameters documented at
88
# docs.mapbox.com/api/search/places. See `custom-openapi/README.md` for
99
# how a file like this is wired in.
10+
#
11+
# One command, not two: the API has a single-id GET and a batch POST, and
12+
# an earlier version of this file exposed both as `places get`/`places
13+
# batch`. Reviewed as the wrong shape for any API with that pair — a
14+
# caller should not have to choose between "one id" and "a JSON array of
15+
# ids" written by hand. `batch` is the one that survives, renamed to the
16+
# bare `places` command via `FLATTENED_SERVICES`, taking one or more ids
17+
# as positional arguments (`VARIADIC_BODY_ARRAY` in `src/spec.rs`) and
18+
# always using the batch endpoint — even for a single id — so there is one
19+
# response shape and one code path. The GET operation this used to expose
20+
# as `places get` is gone from here entirely, not just hidden: nothing
21+
# reaches it.
1022
info:
1123
title: "Mapbox Places API"
1224
description: >-
13-
Full detail for a place — hours, phone, website, photos, address,
14-
coordinates, activity data — by the `mapbox_id` a Search Box API result
15-
already returned. This API has no search or suggest of its own; it only
16-
resolves ids something else found.
25+
Full detail for one or more places — hours, phone, website, photos,
26+
address, coordinates, activity data — by the `mapbox_id`s a Search Box
27+
API result already returned. This API has no search or suggest of its
28+
own; it only resolves ids something else found. Public Preview, with
29+
a 1000-records-per-account monthly quota.
1730
version: "0.0.0"
1831
servers:
1932
- url: https://api.mapbox.com
2033
description: Places API
2134
paths:
22-
/places/v1/details/retrieve/{mapbox_id}:
23-
get:
24-
operationId: get
25-
summary: Full detail for one place.
26-
description: >-
27-
`mapbox_id` comes from a Search Box API result (`search forward`/
28-
`reverse`/`category`) — this API does not look places up by name
29-
or location itself.
30-
parameters:
31-
- name: "mapbox_id"
32-
in: path
33-
required: true
34-
description: "The place's Mapbox ID, from a Search Box API result."
35-
schema:
36-
type: string
37-
minLength: 1
38-
- name: "access_token"
39-
in: query
40-
required: true
41-
description: "Mapbox API Access Token"
42-
schema:
43-
type: string
44-
minLength: 1
45-
responses:
46-
"200":
47-
description: >-
48-
A place record: `name`, `full_address`, `primary_category`/
49-
`categories`, `coordinates` (with `routable_points`),
50-
structured `address`, `score` (`closed`/`reality`/
51-
`popularity`, each 0-1), and where available `brand`,
52-
`opening_hours`, `phone`, `photos`, `website`, `building`, and
53-
`telemetry` (hourly activity by day of week).
54-
"401":
55-
description: Unauthorized
56-
"404":
57-
description: Not Found — no place with that Mapbox ID.
58-
"422":
59-
description: Unprocessable Entity — `mapbox_id` isn't validly formed.
60-
"429":
61-
description: >-
62-
Too Many Requests — the per-second rate limit, or the Public
63-
Preview's monthly quota (1000 records/account), was exceeded.
64-
6535
/places/v1/details/retrieve:
6636
post:
6737
operationId: batch
68-
summary: Full detail for up to 100 places in one call.
38+
summary: Full detail for one or more places.
6939
description: >-
70-
Hydrates a whole list of Search Box API results in one round trip
71-
instead of one `get` per id. `--data` carries `{"ids": [...]}` —
72-
up to 100 `mapbox_id` strings.
40+
Each `mapbox_id` comes from a Search Box API result (`search
41+
forward`/`reverse`/`category`) — this API does not look places up
42+
by name or location itself. Always sends a batch request, one id
43+
or many, so a single id reads the same way, no `get`/`batch`
44+
choice to make. Up to 100 ids in one call.
7345
parameters:
7446
- name: "access_token"
7547
in: query
@@ -84,24 +56,45 @@ paths:
8456
application/json:
8557
schema:
8658
type: object
59+
required: ["ids"]
60+
properties:
61+
ids:
62+
type: array
63+
description: >-
64+
One or more Mapbox IDs, from a Search Box API result,
65+
up to 100.
66+
items:
67+
type: string
68+
minLength: 1
69+
minItems: 1
70+
maxItems: 100
8771
responses:
8872
"200":
8973
description: >-
90-
Every id resolved: `{"results": [<place record>, ...]}`, same
91-
shape as `get`'s own response, one entry per id.
74+
Every id resolved: `{"results": [<place record>, ...]}`, one
75+
entry per id — `name`, `full_address`, `primary_category`/
76+
`categories`, `coordinates` (with `routable_points`),
77+
structured `address`, `score` (`closed`/`reality`/
78+
`popularity`, each 0-1), and where available `brand`,
79+
`opening_hours`, `phone`, `photos`, `website`, `building`, and
80+
`telemetry` (hourly activity by day of week).
9281
"206":
9382
description: >-
9483
Partial Content — some ids didn't resolve. Same body as 200,
9584
plus `missing` (ids with no such place) and/or `unprocessed`
96-
(ids that couldn't be processed) alongside `results`.
85+
(ids that couldn't be processed) alongside `results`. Treated
86+
as success, the same as 200 — a caller still gets every id
87+
that did resolve, and `missing`/`unprocessed` say which ones
88+
didn't rather than failing the whole request over them.
9789
"400":
9890
description: >-
9991
Bad Request — `ids` isn't a non-empty array of at most 100
100-
entries.
92+
entries. The 100 limit is also enforced client-side, before
93+
the request goes out.
10194
"401":
10295
description: Unauthorized
10396
"429":
10497
description: >-
105-
Too Many Requests — the per-second rate limit (batch is
106-
charged by id count) or the Public Preview's monthly quota was
107-
exceeded.
98+
Too Many Requests — the per-second rate limit (charged by id
99+
count) or the Public Preview's monthly quota (1000
100+
records/account) was exceeded.

‎docs/commands.md‎

Lines changed: 51 additions & 103 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Implemented commands
22

3-
Every command the CLI ships: five auth commands, 36 API operations across 11
3+
Every command the CLI ships: five auth commands, 36 API operations across 10
44
command groups, the tilesets-cli proxy, `completion` and `generate-skills`. Each is
55
shown in both of its renderings. Which one you get is decided by `--output`, whose default
66
(`auto`) reads stdout: a terminal gets the left column, a pipe or redirect
@@ -13,8 +13,8 @@ else is as the API sent it.
1313
**32 of the 36 were run against the live API and show what came back:** 25
1414
on 2026-09-01, `fonts list`, `fonts upload` and `fonts delete` on
1515
2026-09-08, once `fonts:list`/`fonts:write` became registrable,
16-
`feedback list` and `feedback get` on 2026-09-24, `places get` and
17-
`places batch` on 2026-09-25, and `feedback create` on 2026-10-02.
16+
`feedback list` and `feedback get` on 2026-09-24, `places` on 2026-09-25,
17+
and `feedback create` on 2026-10-02.
1818
The write operations were exercised as round trips on throwaway objects —
1919
a style created, updated, drafted and deleted; icons uploaded to a sprite
2020
and taken out again; a font uploaded and deleted — leaving the account as
@@ -33,9 +33,9 @@ can be checked cannot fall behind the binary.
3333

3434
The remaining 4 give the response shape from the spec or the docs instead
3535
of a live capture. All four are `search`'s, marked as no-access when this
36-
page was first written — no longer true, discovered while writing `places
37-
get`'s example above, which needed a real `search forward` result to test
38-
against and got one. `search`'s own four sections below haven't been
36+
page was first written — no longer true, discovered while writing
37+
`places`'s example above, which needed a real `search forward` result to
38+
test against and got one. `search`'s own four sections below haven't been
3939
re-captured with this pass, since that's a different command group's page
4040
to touch; a worthwhile follow-up, not done here.
4141

@@ -96,8 +96,7 @@ nests, and is typed `mapbox styles draft get`.
9696
[geocoder.reverse](#mapbox-geocoder-reverse) ·
9797
[geocoder.batch](#mapbox-geocoder-batch)
9898

99-
**[Places](#places)** — [places.get](#mapbox-places-get) ·
100-
[places.batch](#mapbox-places-batch)
99+
**[Places](#places)** — [places](#mapbox-places)
101100

102101
**[Search](#search)** — [search.forward](#mapbox-search-forward) ·
103102
[search.reverse](#mapbox-search-reverse) ·
@@ -1415,134 +1414,83 @@ single query.
14151414
---
14161415
## Places
14171416

1418-
Full detail for a place — hours, phone, website, photos, address,
1419-
coordinates, activity data — by the `mapbox_id` a Search Box API result
1420-
already returned. Curated by hand down to the parameters documented at
1421-
docs.mapbox.com/api/search/places — see `custom-openapi/README.md` for why
1422-
this command group doesn't come from the vendored specs the way most
1423-
others do.
1417+
Full detail for one or more places — hours, phone, website, photos,
1418+
address, coordinates, activity data — by the `mapbox_id`s a Search Box
1419+
API result already returned. Curated by hand down to the parameters
1420+
documented at docs.mapbox.com/api/search/places — see
1421+
`custom-openapi/README.md` for why this command group doesn't come from
1422+
the vendored specs the way most others do. Public Preview, with a
1423+
1000-records-per-account monthly quota.
14241424

14251425
**This API has no search or suggest of its own.** It only resolves ids
14261426
`search forward`/`reverse`/`category` already returned — the detail-view
14271427
follow-up to a search result, not a way to find places by name or location.
14281428

1429-
### `mapbox places get`
1429+
### `mapbox places`
14301430

1431-
Full detail for one place.
1431+
One or more full place records. No subcommand: earlier versions of this
1432+
page split this into `places get` (one id) and `places batch` (a JSON
1433+
array of ids) — reviewed as the wrong shape for any API with that pair, a
1434+
caller should not have to choose. `mapbox places` always sends the batch
1435+
request, one id or many, so there is one command and one response shape.
14321436

14331437
#### Parameters
14341438

1435-
`<mapbox-id>` (positional) is required — from a Search Box API result.
1439+
`<mapbox-id>...` (positional, one or more, up to 100) — each from a
1440+
Search Box API result. More than 100 is a usage error before the request
1441+
goes out.
14361442

14371443
#### Examples
14381444

14391445
```sh
1440-
mapbox places get dXJuOm1ieHBvaTpmYTE5Y2NhMC0yZmQ3LTQwMzgtYTEzNy02MzFmNGEwZDI5ODA
1446+
mapbox places dXJuOm1ieHBvaTpmYTE5Y2NhMC0yZmQ3LTQwMzgtYTEzNy02MzFmNGEwZDI5ODA
1447+
mapbox places dXJuOm1ieHBvaTpmYTE5Y2NhMC0yZmQ3LTQwMzgtYTEzNy02MzFmNGEwZDI5ODA \
1448+
dXJuOm1ieHBvaTo4N2YzMmY2YS00MjkwLTQzNmItYWQyMi1hMzBhMzcxNWVmNzM
14411449
```
14421450

14431451
#### Outputs
14441452

1445-
Captured live, a real place found via `search forward --q "Ferry Building
1446-
San Francisco"`:
1453+
The response is always `{"results": [<place record>, ...]}`, one entry
1454+
per id, each carrying `name`, `full_address`, `primary_category`/
1455+
`categories`, `coordinates` (with `routable_points`), structured
1456+
`address`, `score` (`closed`/`reality`/`popularity`, each 0-1), and where
1457+
available `brand`, `opening_hours`, `phone`, `photos`, `website`,
1458+
`building`, and `telemetry` (hourly activity by day of week).
14471459

1448-
<table>
1449-
<tr><th width="50%">Terminal — <code>-o text</code></th><th width="50%">Agent — <code>-o json</code></th></tr>
1450-
<tr><td>
1451-
1452-
```json
1453-
{
1454-
"mapbox_id": "dXJuOm1ieHBvaTpmYTE5Y2NhMC0yZmQ3LTQwMzgtYTEzNy02MzFmNGEwZDI5ODA",
1455-
"name": "Ferry Building",
1456-
"full_address": "San Francisco, California, 94105, United States",
1457-
"primary_category": "food",
1458-
"categories": ["cafe", "food", "food_and_drink"],
1459-
"status": "active",
1460-
"permanently_closed": false,
1461-
"opening_hours": "Sa 08:00-14:00",
1462-
"phone": "+14152373318",
1463-
"website": "http://crumbleandwhisk.com/",
1464-
"score": { "closed": 0, "reality": 0.973, "popularity": 0.275 },
1465-
"coordinates": {
1466-
"latitude": 37.79557765,
1467-
"longitude": -122.39332918,
1468-
"source": "poi",
1469-
"routable_points": [
1470-
{ "name": "driving", "latitude": 37.795594, "longitude": -122.393338 }
1471-
]
1472-
},
1473-
"address": {
1474-
"city": "San Francisco",
1475-
"neighborhood": "Financial District",
1476-
"postcode": "94105",
1477-
"region": "California",
1478-
"region_code_full": "US-CA",
1479-
"country": "United States",
1480-
"country_code": "US"
1481-
}
1482-
}
1483-
```
1484-
1485-
</td><td>
1486-
1487-
```json
1488-
{"mapbox_id":"dXJuOm1ieHBvaTpmYTE5Y2NhMC0yZmQ3LTQwMzgtYTEzNy02MzFmNGEwZDI5ODA","name":"Ferry Building","full_address":"San Francisco, California, 94105, United States","primary_category":"food","categories":["cafe","food","food_and_drink"],"status":"active","permanently_closed":false,"opening_hours":"Sa 08:00-14:00","phone":"+14152373318","website":"http://crumbleandwhisk.com/","score":{"closed":0,"reality":0.973,"popularity":0.275},"coordinates":{"latitude":37.79557765,"longitude":-122.39332918,"source":"poi","routable_points":[{"name":"driving","latitude":37.795594,"longitude":-122.393338}]},"address":{"city":"San Francisco","neighborhood":"Financial District","postcode":"94105","region":"California","region_code_full":"US-CA","country":"United States","country_code":"US"}}
1489-
```
1490-
1491-
</td></tr>
1492-
</table>
1493-
1494-
Dropped `attributes` (14 boolean amenity flags — wheelchair access, payment
1495-
types, and the like), `created_at`/`updated_at`, and most `address` fields
1496-
that were `null` for this place, for length; the real response carries
1497-
them too. `brand` is `null` here since this isn't a chain location.
1498-
1499-
### `mapbox places batch`
1500-
1501-
Full detail for up to 100 places in one call — hydrates a whole list of
1502-
search results in one round trip instead of one `get` per id.
1503-
1504-
#### Parameters
1505-
1506-
`--data`/`-d` carries `{"ids": [...]}`, up to 100 `mapbox_id` strings.
1507-
1508-
#### Examples
1509-
1510-
```sh
1511-
mapbox places batch -d '{"ids": ["dXJuOm1ieHBvaTpmYTE5Y2NhMC0yZmQ3LTQwMzgtYTEzNy02MzFmNGEwZDI5ODA", "dXJuOm1ieHBvaTo4N2YzMmY2YS00MjkwLTQzNmItYWQyMi1hMzBhMzcxNWVmNzM"]}'
1512-
```
1513-
1514-
#### Outputs
1515-
1516-
Captured live, the same Ferry Building above plus a second real place:
1460+
A single real place record below — the same Ferry Building result a
1461+
previous pass of this page captured live via `search forward --q "Ferry
1462+
Building San Francisco"` — run through this CLI's own output rendering
1463+
again to show both modes honestly for the one-command shape, rather than
1464+
reused verbatim from the old two-command page. Not re-captured from a
1465+
live call: this environment has no network access to the real API.
15171466

15181467
<table>
15191468
<tr><th width="50%">Terminal — <code>-o text</code></th><th width="50%">Agent — <code>-o json</code></th></tr>
15201469
<tr><td>
15211470

1522-
```json
1523-
{
1524-
"results": [
1525-
{ "mapbox_id": "…fa19cca0…", "name": "Ferry Building" },
1526-
{ "mapbox_id": "…87f32f6a…", "name": "Golden Gate Bridge" }
1527-
]
1528-
}
1471+
```text
1472+
NAME FULL_ADDRESS MAPBOX_ID OPENING_HOURS PERMANENTLY_CLOSED PHONE PRIMARY_CATEGORY STATUS WEBSITE
1473+
Ferry Building San Francis… dXJuOm1i… Sa 08:00-14:… no +141523… food active http://…
15291474
```
15301475

15311476
</td><td>
15321477

15331478
```json
1534-
{"results":[{"mapbox_id":"…fa19cca0…","name":"Ferry Building"},{"mapbox_id":"…87f32f6a…","name":"Golden Gate Bridge"}]}
1479+
{"results":[{"mapbox_id":"dXJuOm1ieHBvaTpmYTE5Y2NhMC0yZmQ3LTQwMzgtYTEzNy02MzFmNGEwZDI5ODA","name":"Ferry Building","full_address":"San Francisco, California, 94105, United States","primary_category":"food","categories":["cafe","food","food_and_drink"],"status":"active","permanently_closed":false,"opening_hours":"Sa 08:00-14:00","phone":"+14152373318","website":"http://crumbleandwhisk.com/","score":{"closed":0,"reality":0.973,"popularity":0.275},"coordinates":{"latitude":37.79557765,"longitude":-122.39332918,"source":"poi"},"address":{"city":"San Francisco","region":"California"}}]}
15351480
```
15361481

15371482
</td></tr>
15381483
</table>
15391484

1540-
Each entry in `results` is the full record `get` returns, trimmed to
1541-
`mapbox_id`/`name` here for length. Not captured live: `206` with
1542-
`missing`/`unprocessed` alongside `results` — the documented shape for a
1543-
batch where some ids didn't resolve. Both ids used to write this page were
1544-
real and resolved, so triggering it would have meant fabricating a
1545-
plausibly-shaped but fake id, which defeats the point of a live capture.
1485+
The response's own nested objects (`coordinates`, `address`, `score`) and
1486+
arrays (`categories`, `photos`) are dropped from the table — only scalar
1487+
fields become columns, the same rule every table on this page follows —
1488+
so the full detail is a `-o json` away. A call with more than one id adds
1489+
one row per place to the same table, same columns. `206` with
1490+
`missing`/`unprocessed` alongside `results` — some ids didn't resolve — is
1491+
documented but not shown here, for the same reason: no live call to
1492+
trigger it from, and fabricating a plausibly-shaped but fake id to force
1493+
one would defeat the point of a real capture.
15461494

15471495
---
15481496

0 commit comments

Comments
 (0)