summaryrefslogtreecommitdiffstats
path: root/README.md
diff options
context:
space:
mode:
authorMurilo Ijanc <[email protected]>2026-07-26 12:42:37 -0300
committerMurilo Ijanc <[email protected]>2026-07-26 12:42:37 -0300
commit11d49bc25eb1643f1b742f9527212d8781f2e0de (patch)
treebae1cb090cbf6c9f82ab5abbaf1194206f92f539 /README.md
downloadr-11d49bc25eb1643f1b742f9527212d8781f2e0de.tar.gz
Initial importv0.1.0
Diffstat (limited to 'README.md')
-rw-r--r--README.md207
1 files changed, 207 insertions, 0 deletions
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..64baf53
--- /dev/null
+++ b/README.md
@@ -0,0 +1,207 @@
+# cepis
+
+R client for the [public CEP API at cep.is](https://cep.is). Official
+Correios data (DNE), refreshed every two weeks.
+
+```r
+library(cepis)
+
+address <- cep_lookup("01001-000")
+cat(address$logradouro, address$localidade, address$uf)
+#> Praça da Sé São Paulo SP
+```
+
+## Installation
+
+```r
+remotes::install_git("https://git.cep.is/r.git")
+```
+
+Requires R 4.0 or newer, plus two packages: **curl**, because base R cannot
+report a response status code and this client has to tell a 404 from a 400
+from a 5xx, and **jsonlite** to read the JSON. Both are the ecosystem
+standard and neither pulls a stack behind it. CSV is parsed with
+`utils::read.csv()`.
+
+## Usage
+
+### A single lookup
+
+`cep_lookup()` accepts the CEP masked or unmasked and returns a
+`cepis_address`, a plain list:
+
+```r
+a <- cep_lookup("01310-100")
+
+a$logradouro # "Avenida Paulista"
+a$localidade # "São Paulo"
+a$uf # "SP"
+a$ibge # "3550308"
+a$cep # "01310100" (always unmasked)
+formatted_cep(a) # "01310-100"
+```
+
+Fields the database does not have for that CEP come back as empty strings,
+never `NA`. `a$raw` holds the response as it arrived, including any field the
+API grew after this release.
+
+### Coordinates
+
+About 57% of CEPs have a coordinate. When they do not, `geo` is `NULL`:
+
+```r
+a <- cep_lookup("01310100")
+
+if (!is.null(a$geo)) {
+ a$geo$lat # -23.567202
+ a$geo$lon # -46.649659
+ a$geo$raio_m # 1084
+ a$geo$precisao # "logradouro"
+}
+```
+
+**Check `precisao` before computing distance.** There are two kinds of point,
+and the difference between them is tens of kilometres:
+
+| `precisao` | typical radius | what the point is |
+| --- | --- | --- |
+| `logradouro` | 110–140 m | the stretch of street — good for maps and distance |
+| `localidade` | 7–40 km | the municipality centroid: the CEP *is* the whole city |
+
+`a$geo$is_precise` is the shorthand for `precisao == "logradouro"`:
+
+```r
+if (!is.null(a$geo) && a$geo$is_precise) {
+ cost <- shipping_from(a$geo$lat, a$geo$lon)
+} else {
+ cost <- shipping_from_municipality(a$ibge)
+}
+```
+
+### Many CEPs at once
+
+Do not loop over `cep_lookup()`. Use `cep_lookup_many()`, which resolves up
+to 500 CEPs per request:
+
+```r
+ceps <- c("01001000", "99999999", "20040020")
+result <- cep_lookup_many(ceps)
+
+for (i in seq_along(ceps)) {
+ if (is.null(result[[i]])) {
+ cat(ceps[i], "not found\n")
+ } else {
+ cat(ceps[i], result[[i]]$localidade, "\n")
+ }
+}
+```
+
+The returned list has **the same length and the same order** as the input. A
+CEP that does not exist, or is malformed, is `NULL` at its position — it does
+not vanish, and it does not raise. A list rather than a `data.frame` because
+`NULL` is the only unambiguous "no such CEP" in R: a row of `NA`s reads as an
+address with unknown fields, which is a different statement.
+
+Vectors longer than 500 are chunked automatically.
+
+> **Batches carry no coordinate.** The endpoint behind them is CSV, and the
+> CSV has no `geo` column. Every address from `cep_lookup_many()` has
+> `geo = NULL`, including for CEPs that do have one. Need `geo`? Use
+> `cep_lookup()`, one at a time.
+
+### API health
+
+Useful for two things: checking the API answers, and knowing **which version
+of the database** is live:
+
+```r
+h <- cep_health()
+h$ok # TRUE
+h$dne # "26071" identifier of the fortnightly database
+h$ceps # 1596168
+h$base_gerada_em # "2026-07-24T03:48:28Z"
+```
+
+An API reporting itself degraded does not raise — that arrives in `h$status`.
+
+## Configuration
+
+For anything beyond the defaults, build a client with `cepis_client()` and
+pass it along. It holds configuration only, keeps no state between calls and
+is safe to reuse:
+
+```r
+client <- cepis_client(
+ base_url = "https://cep.is", # point at another instance
+ timeout = 10, # seconds for one attempt
+ max_attempts = 3L, # counting the first; 1 disables retrying
+ backoff_base = 0.5, # base of the exponential wait
+ backoff_max = 8, # cap on each wait
+ total_timeout = NULL # cap on the whole call, attempts included
+)
+
+address <- cep_lookup("01001000", client)
+```
+
+Retries cover 429, 5xx and network failures, with exponential backoff and
+jitter; 400 and 404 are never retried. See `?cepis_client` for the details of
+each parameter.
+
+Omitting `client` uses a shared default one, built with the defaults above.
+
+### Pointing at a local instance
+
+`base_url` is the only parameter that usually changes between environments.
+The API repo brings a local instance up with `just up`:
+
+```r
+client <- cepis_client(base_url = "http://localhost:8080")
+```
+
+## Errors
+
+Failures are classed conditions. Everything inherits `cepis_error` (and
+`error`), so one handler catches all of them. Below that, the split is by
+**the decision you have to make**, not by the HTTP status that caused it:
+
+| class | when | does retrying help? |
+| --- | --- | --- |
+| `cepis_invalid_cep` | not 8 digits | no |
+| `cepis_not_found` | well-formed, not in the database | no |
+| `cepis_rate_limited` | 429; carries `retry_after` | yes, later |
+| `cepis_server_error` | 5xx; carries `status` | yes, later |
+| `cepis_network_error` | DNS, connection, timeout, TLS | yes, later |
+| `cepis_invalid_response` | unintelligible response | probably not |
+
+```r
+tryCatch(
+ cep_lookup(cep_from_form),
+ cepis_not_found = function(e) NULL, # ask the user for another CEP
+ cepis_error = function(e) reschedule(e) # transient failure: retry later
+)
+```
+
+`cepis_invalid_response` almost always means something between you and the
+API is answering instead of it — a captive Wi-Fi portal, a corporate proxy,
+an HTML error page. Useful as a diagnosis: the problem is not in your code.
+
+## Development
+
+Everything runs through `make`:
+
+```sh
+make # list the targets
+make test # install into check/lib and run the suite
+make lint # parse + codetools, touches nothing
+make document # regenerate man/ and NAMESPACE with roxygen2
+make build # build the source tarball
+make check # lint, then test, then build
+make check-cran # build, then R CMD check the tarball
+```
+
+The suite stands up a real HTTP server on loopback and points `base_url` at
+it, so no test touches the network.
+
+## Licence
+
+ISC. See [LICENSE](LICENSE).