---
title: "Efficient token use"
output: rmarkdown::html_vignette
vignette: >
  %\VignetteIndexEntry{Efficient token use}
  %\VignetteEngine{knitr::rmarkdown}
  %\VignetteEncoding{UTF-8}
---

```{r, include = FALSE}
knitr::opts_chunk$set(
  collapse = TRUE,
  comment = "#>",
  eval = nzchar(Sys.getenv("CLOSECITY_KEY"))
)
library(closecity)
close <- closecity::close_client(api_key = Sys.getenv("CLOSECITY_KEY"))
```

The API is metered in tokens: one per returned row, minimum one per request, and
10 per isochrone contour. Tokens are rows, so spending well is mostly about not
asking for rows you will throw away.

## Free queries

Metadata queries are free and keyless: `$modes()`, `$destination_types()`,
`$vintage()`, `$places()`, `$isochrone_meta()`, `$last_updated()`, and `$health()`.
Do your lookups before you spend a token.

Revalidation is free too. Keep a reply's `etag` and pass it back as
`if_none_match`; an unchanged response returns a free `304`. Read `output = "raw"`
when you want the etag:

```{r}
close$output <- "raw"
first <- close$block_summary(geoid = "440070008001068", mode = "walk")
again <- close$block_summary(
  geoid = "440070008001068",
  mode = "walk",
  if_none_match = first$etag
)
again$not_modified   # TRUE, and nothing was charged
```

```{r, include = FALSE}
close$output <- "spatial"
```

## The big levers

The rows a query returns multiply out as modes times types times blocks:

- **Pass `mode`.** Omitting it returns all three modes, so three times the rows.
- **Filter by `type`.** The POI and areal routes take a destination `type`, so ask
  the server for only the category you want rather than fetching everything and
  filtering in R. Look the id up in `$destination_types()`.
- **Request leaf types, not parents.** A parent type expands to its leaves, so a
  single leaf id is a fraction of the rows of a parent like `parks`.
- **Shrink the area.** Cost grows with the square of `radius_m`, so halving the
  radius is roughly a quarter of the tokens.
- **Use `max_minutes`.** It drops rows you would filter out anyway.

## Isochrones are the cheap reach primitive

An isochrone is 10 tokens per contour regardless of area, and `format = "blocks"`
returns every reachable block with its minutes. A whole walkshed for 10 tokens,
where a catchment or block query charges per block:

```{r}
shed <- close$isochrone(
  block = "440070008001068",
  minutes = 30,
  mode = "walk",
  format = "blocks"
)
```

## Watch what you spend

Every metered reply carries the token counts. In the frame output modes they are
attached as attributes; in `output = "raw"` they are on the reply. Here the `type`
filter asks only for supermarkets, so you pay for a handful of rows, not every POI
in the radius:

```{r}
close$output <- "tabular"
types <- close$destination_types()
supermarket_dest_type <- types[types$label == "grocery_stores", ]$dest_type_id

supermarkets <- close$pois_search(
  lat = 41.823,
  lon = -71.412,
  radius_m = 1200,
  type = supermarket_dest_type
)
attr(supermarkets, "tokens_charged")
attr(supermarkets, "tokens_remaining")
```

When you only want the numbers, `output = "tabular"` skips the block-boundary
download entirely. The block boundaries the spatial mode joins are cached locally,
so re-running a script costs time, not tokens; pass a `block_geometry` frame you
already have to skip even that.
