Give Any REST API a Command Line

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

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:

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}:

{
  "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:

aux4 users list
 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:

aux4 users list | aux4 json get '$.0'
{
  "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:

aux4 users get 1
{
  "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:

aux4 posts list --user 1
 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:

# 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:

aux4 test run api.test.md
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