*! version 0.1.0 2026-07-04

Title

webapi -- Fetch an authenticated JSON web API into Stata as a dataset

Syntax

webapi get using "URL" [, options]

webapi post using "URL" [, body(text) options]

Shape the reply
records(path) dot-path to the JSON array of records; default is the whole reply

stringall import every column as a string (skip type detection)

clear replace the data in memory (as import delimited)

Request
params(k=v|k=v) pipe-delimited query-string parameters

headers(H:V|H:V) pipe-delimited request headers

body(text) request body for post (JSON content-type assumed)

every(#) poll every # seconds; snapshots stack into a panel

times(#) number of polls (default 12 when every() is set)

timeout(#) seconds before giving up (default 30)

tsv(path) keep the intermediate TSV at this path

verbose echo the underlying python invocation

Authentication
bearer(token) send Authorization: Bearer token

basicauth(user:pw) send HTTP basic authentication

apikeyheader(H:V) send an API key as a header

apikeyparam(k=v) send an API key as a query parameter

Description

webapi performs one HTTP request to a JSON web API, authenticates it,
and reads the reply back into Stata as variables and observations. It is a
small, dependency-free generalisation of the pattern documented for
googlesheets and googlechart: a request to a URL, an
authentication step, and a JSON reply that must be parsed into memory.

The network and JSON work is done by a companion Python helper that uses only
the Python standard library (no pip packages, no virtualenv), so the only
requirement is a working python3 on the system PATH. The helper selects the array of records named by records(),
flattens each record's nested scalar fields into dotted column names, and
writes a tab-separated table that webapi imports with
import delimited.

For JSON parsing outside a web-request context, see also
jsonio (Buchanan) and
insheetjson / libjson (Lindsley);
webapi focuses on the authenticated request-and-shape workflow rather
than on JSON parsing as such.

Options

records(path) names the JSON array to turn into rows.
Use a dot path when the array is nested, for example records("data") or
records("results.items"). Omit it (or pass records("")) when the
reply is itself the array of records.

params() and headers() are pipe-delimited so that values may
contain spaces: params("q=heart disease|limit=50"),
headers("Accept:application/json|X-App-Token:abc"). Parameter values are
URL-encoded for you.

Authentication

Pick whichever the API expects. bearer() sets an
Authorization: Bearer header; apikeyheader() and
apikeyparam() carry an API key in a header or the query string;
basicauth() sends HTTP basic credentials. A public API needs none of
these. Store secrets in a global (for example $API_TOKEN set in your
profile.do) and pass bearer("$API_TOKEN") rather than pasting the
token into the do-file.

Stored results

webapi stores in r():

Scalars

r(nrows) number of records read; on a polling call, the total
rows accumulated across all polls

r(ncols) number of columns (single-request calls only; not
returned when polling)

r(polls) number of polls completed (polling calls only)

Note: a polling call (every() with times()) always replaces the
data in memory, whether or not clear is specified.

Macros

r(http) HTTP status code (of the last poll when polling)

r(url) the request URL

Examples

A public array of records (nested fields flatten automatically):

. webapi get using "https://jsonplaceholder.typicode.com/users", clear

A public health API (CDC NCHS leading causes of death, on Socrata), with
field filters and a header:

. webapi get using "https://data.cdc.gov/resource/bi63-dtpu.json", ///

params("year=2017|cause_name=All causes") headers("Accept:application/json") clear

An authenticated API, with the token kept out of the script:

. webapi get using "https://api.example.org/v1/series", ///

bearer("$API_TOKEN") params("since=2026-01-01") records("data") clear

Create a resource with post; the reply is the created object:

. webapi post using "https://api.example.org/v1/items", ///

body(`"{"name":"Texas","value":18.8}"') bearer("$API_TOKEN") records("") clear

Poll a live endpoint on an interval; each snapshot stacks into a panel
keyed by _poll and _polltime:

. webapi get using "https://api.example.org/v1/status", ///

records("data") every(30) times(20) clear

Remarks

A note on the dollar sign. Some APIs (Socrata's $limit,
$select, $where) use parameters that begin with $, which
Stata reads as a global-macro reference. Use plain field filters where you
can, or write the dollar sign as `=char(36)' inside params().

Privacy. Do not place secrets in URL query strings if a header will do,
and never commit a token to a public repository.

Author

Eric A. Booth, Sr Researcher, Texas2036.org (eric.a.booth@gmail.com), 2026.
MIT-licensed. A companion to googlesheets and googlechart.