# Give Any REST API a Command Line ![banner](https://articles.aux4.blog/data/posts/aux4/give-any-rest-api-a-command-line/banner.png) ## Introduction Every team has an API they hit all day, and everyone re-types the same `curl` with the same base URL and pipes it through the same JSON parser. This post wraps a REST API in a small CLI — so you run `aux4 users list` and get a clean table in your terminal, or pipe it to get JSON for the next tool — and then verifies that CLI against the live API with `aux4/test`, so the wrapper can't silently rot. We'll wrap the public demo API at `https://jsonplaceholder.typicode.com` (no auth, predictable data), but the shape works for any API. ## Install the packages ```bash aux4 aux4 pkger install aux4/curl aux4/json aux4/render aux4/config aux4/test ``` ## Step 1: put the base URL in a config file Keep the base URL in one place so it isn't repeated in every command. Create `config.yaml` — note the required top-level `config:` key: ```yaml config: baseUrl: https://jsonplaceholder.typicode.com ``` ## Step 2: wrap the endpoints as commands Create a `.aux4` file. Each command fetches with `aux4 curl request`, projects the fields with `aux4 json`, and captures the result as JSON with the `json:` executor. The `render` field then decides how it's shown: `tty` renders a table when you're in a terminal, and piping the command gives the raw JSON instead. A `configFile` variable (defaulted to `config.yaml`) auto-populates `${baseUrl}`: ```json { "profiles": [ { "name": "main", "commands": [ { "name": "users", "execute": [ "profile:users" ], "help": { "text": "Work with users from the API" } }, { "name": "posts", "execute": [ "profile:posts" ], "help": { "text": "Work with posts from the API" } } ] }, { "name": "users", "commands": [ { "name": "list", "execute": [ "json:aux4 curl request ${baseUrl}/users | aux4 json select --structure id,name,email" ], "render": { "tty": "aux4 render table id,name,email", "table": "aux4 render table id,name,email" }, "help": { "text": "List all users", "variables": [ { "name": "configFile", "text": "Config file with baseUrl", "default": "config.yaml" } ] } }, { "name": "get", "execute": [ "aux4 curl request ${baseUrl}/users/${id} | aux4 json pretty" ], "help": { "text": "Get a single user by id", "variables": [ { "name": "id", "text": "User id", "arg": true }, { "name": "configFile", "text": "Config file with baseUrl", "default": "config.yaml" } ] } } ] }, { "name": "posts", "commands": [ { "name": "list", "execute": [ "json:aux4 curl request ${baseUrl}/users/${user}/posts | aux4 json select --structure id,title" ], "render": { "tty": "aux4 render table id,title", "table": "aux4 render table id,title" }, "help": { "text": "List posts written by a given user", "variables": [ { "name": "user", "text": "User id whose posts to list" }, { "name": "configFile", "text": "Config file with baseUrl", "default": "config.yaml" } ] } } ] } ] } ``` aux4 auto-loads the `.aux4` in your working directory. In a terminal, `aux4 users list` renders a table: ```bash aux4 users list ``` ```text id name email 1 Leanne Graham Sincere@april.biz 2 Ervin Howell Shanna@melissa.tv 3 Clementine Bauch Nathan@yesenia.net 4 Patricia Lebsack Julianne.OConner@kory.org 5 Chelsey Dietrich Lucio_Hettinger@annie.ca 6 Mrs. Dennis Schulist Karley_Dach@jasper.info 7 Kurtis Weissnat Telly.Hoeger@billy.biz 8 Nicholas Runolfsdottir V Sherwood@rosamond.me 9 Glenna Reichert Chaim_McDermott@dana.io 10 Clementina DuBuque Rey.Padberg@karina.biz ``` Pipe the exact same command into another tool, and you get JSON instead of the table — the `render` field only formats for a terminal: ```bash aux4 users list | aux4 json get '$.0' ``` ```json { "email": "Sincere@april.biz", "id": 1, "name": "Leanne Graham" } ``` That duality is what makes it a real CLI: readable for a human, structured for a pipe. Fetch one user by id: ```bash aux4 users get 1 ``` ```json { "address": { "city": "Gwenborough", "geo": { "lat": "-37.3159", "lng": "81.1496" }, "street": "Kulas Light", "suite": "Apt. 556", "zipcode": "92998-3874" }, "company": { "bs": "harness real-time e-markets", "catchPhrase": "Multi-layered client-server neural-net", "name": "Romaguera-Crona" }, "email": "Sincere@april.biz", "id": 1, "name": "Leanne Graham", "phone": "1-770-736-8031 x56442", "username": "Bret", "website": "hildegard.org" } ``` And a nested endpoint — one user's posts: ```bash aux4 posts list --user 1 ``` ```text id title 1 sunt aut facere repellat provident occaecati excepturi optio reprehenderit 2 qui est esse 3 ea molestias quasi exercitationem repellat qui ipsa sit aut 4 eum et est occaecati 5 nesciunt quas odio 6 dolorem eum magni eos aperiam quia 7 magnam facilis autem 8 dolorem dolore est ipsam 9 nesciunt iure omnis dolorem tempora et accusantium 10 optio molestias id quia eum ``` No one on your team needs to remember the base URL, the path, or how to parse the JSON — just `aux4 users list`. ## Step 3: test the CLI against the live API A wrapper is only useful if it keeps working. `aux4/test` runs markdown tests: an `execute` block runs a command, an `expect` block asserts the output. Because tests run outside a terminal, ask for the table explicitly with `--render table`, and use `expect:partial` with `**` wildcards to match stable facts (a known user) instead of volatile whole-response output. Create `api.test.md`: ````markdown # REST API CLI ## users list ### should list users including a known one ```execute aux4 users list --render table ``` ```expect:partial **Leanne Graham**Sincere@april.biz** ``` ## users get ### should fetch a single user by id ```execute aux4 users get 1 ``` ```expect:partial **"city": "Gwenborough"**"email": "Sincere@april.biz"**"name": "Leanne Graham"** ``` ```` Run it: ```bash aux4 test run api.test.md ``` ```text Test Suites: 1 passed, 1 total Tests: 2 passed, 2 total ``` The tests call your CLI, which calls the real API — so if the endpoint changes shape or your wrapper breaks, the test goes red. Wire `aux4 test run` into CI and the CLI is verified on every change. ## Conclusion You turned a REST API into a small CLI: `aux4/curl` makes the request, `aux4/json` projects the response, and `aux4/render` shows a table in a terminal while keeping the piped output as JSON — all from a couple of commands in one `.aux4` file. Then `aux4/test` verifies the wrapper against the live API, so it stays correct. Point the same pattern at your own API and your team stops memorizing `curl`. ## See Also - [aux4/curl](https://hub.aux4.io/r/public/packages/aux4/curl) - [aux4/json](https://hub.aux4.io/r/public/packages/aux4/json) - [aux4/render](https://hub.aux4.io/r/public/packages/aux4/render) - [aux4/config](https://hub.aux4.io/r/public/packages/aux4/config) - [aux4/test](https://hub.aux4.io/r/public/packages/aux4/test)