Introduction

Slumber is a terminal-based HTTP client, built for interacting with REST and other HTTP clients. It has two usage modes: Terminal User Interface (TUI) and Command Line Interface (CLI). The TUI is the most useful, and allows for interactively sending requests and viewing responses. The CLI is useful for sending quick requests and scripting.

The goal of Slumber is to be easy to use, configurable, and sharable. To that end, configuration is defined in a YAML file called the request collection. Both usage modes share the same basic configuration, which is called the request collection.

Check out the Getting Started guide to try it out, or move onto Key Concepts to start learning in depth about Slumber.

Install

See installation instructions

Getting Started

Quick Start

Once you've installed Slumber, setup is easy.

1. Create a Slumber collection file

Create a file called slumber.yml and add the following contents:

requests:
  get:
    method: GET
    url: https://httpbin.org/get

2. Run Slumber

slumber

Going Further

Here's a more complete example:

# slumber.yml
profiles:
  local:
    data:
      host: http://localhost:5000
  production:
    data:
      host: https://myfishes.fish

requests:
  create_fish:
    method: POST
    url: "{{host}}/fishes"
    body: >
      {"kind": "barracuda", "name": "Jimmy"}

  list_fish:
    method: GET
    url: "{{host}}/fishes"

This request collection uses templates and profiles allow you to dynamically change the target host.

Key Concepts

There are a handful of key concepts you need to understand how to effectively configure and use Slumber. You can read about each one in detail on its linked API reference page.

Request Collection

The collection is the main form of configuration. It defines a set of request recipes, which enable Slumber to make requests to your API.

Request Recipe

A recipe defines which HTTP requests Slumber can make. A recipe generally correponds to one endpoint on an API, although you can create as many recipes per endpoint as you'd like.

Template

Templates are Slumber's most powerful feature. They allow you to dynamically build URLs, query parameters, request bodies, etc. using predefined or dynamic values.

Profile

A profile is a set of static template values. A collection can contain a list of profiles, allowing you to quickly switch between different sets of values. This is useful for using different deployment environments, different sets of IDs, etc.

Terminal User Interface

The Terminal User Interface (TUI) is the primary use case for Slumber. It provides a long-lived, interactive interface for sending HTTP requests, akin to Insomnia or Postman. The difference of course is Slumber runs entirely in the terminal.

To start the TUI, simply run:

slumber

This will detect your request collection file according to the protocol. If you want to load a different file, you can use the --collection parameter:

slumber --collection my-slumber.yml

Auto-Reload

Once you start your Slumber, that session is tied to a single collection file. Whenever that file is modified, Slumber will automatically reload it and changes will immediately be reflected in the TUI. If auto-reload isn't working for some reason, you can manually reload the file with the r key.

Multiple Sessions

Slumber supports running multiple sessions at once, even on the same collection. Request history is stored in a thread-safe SQLite, so multiple sessions can safely interact simultaneously.

If you frequently run multiple sessions together and want to quickly switch between them, consider a configurable terminal manager like tmux or Zellij.

Command Line Interface

While Slumber is primary intended as a TUI, it also provides a Command Line Interface (CLI). The CLI can be used to send requests, just like the TUI. It also provides some utility commands for functionality not available in the TUI. For a full list of available commands, run:

slumber help

Sending Requests

There are many use cases to which the CLI is better suited than the TUI, including:

  • Sending a single one-off request
  • Sending many requests in parallel
  • Automating requests in a script

Given this request collection:

profiles:
  production:
    data:
      host: https://myfishes.fish

requests:
  list_fish:
    method: GET
    url: "{{host}}/fishes"

You can use the request subcommand:

slumber request --profile production list_fishes
slumber rq -p production list_fishes # This is equivalent, just shorter

Exit Code

By default, the CLI returns exit code 1 if there is a fatal error, e.g. the request failed to build or a network error occurred. If an HTTP response was received and parsed, the process will exit with code 0, regardless of HTTP status.

If you want to set the exit code based on the HTTP response status, use the flag --exit-code.

CodeReason
0HTTP response received
1Fatal error
2HTTP response had status >=400 (with --exit-code)

Templates

Templates enable dynamic string construction. Slumber's template language is relatively simple, compared to complex HTML templating languages like Handlebars or Jinja. The goal is to be intuitive and unsurprising. It doesn't support complex features like loops, conditionals, etc.

Most string values (i.e. not keys) in a request collection are templates, meaning they support templating. The syntax for templating a value into a string is double curly braces {{...}}. The contents inside the braces tell Slumber how to retrieve the dynamic value.

This guide serves as a functional example of how to use templates. For detailed information on options available, see the API reference.

A Basic Example

Let's start with a simple example. Let's say you're working on a fish-themed website, and you want to make requests both to your local stack and the deployed site. Templates, combined with profiles, allow you to easily switch between hosts:

Note: for the purposes of these examples, I've made up some theoretical endpoints and responses, following standard REST practice. This isn't a real API but it should get the point across.

Additionally, these examples will use the CLI because it's easy to demonstrate in text. All these concepts apply equally to the TUI.

profiles:
  local:
    data:
      host: http://localhost:5000
  production:
    data:
      host: https://myfishes.fish

requests:
  list_fish:
    method: GET
    url: "{{host}}/fishes"

Now you can easily select which host to hit. In the TUI, this is done via the Profile list. In the CLI, use the --profile option:

> slumber request --profile local list_fish
# http://localhost:5000/fishes
# Only one fish :(
[{"id": 1, "kind": "tuna", "name": "Bart"}]
> slumber request --profile production list_fish
# https://myfishes.fish/fishes
# More fish!
[
  {"id": 1, "kind": "marlin", "name": "Kim"},
  {"id": 2, "kind": "salmon", "name": "Francis"}
]

Chaining Requests

Profile values are helpful when you want to switch between statically known values, but what if you need a value from a different response? Let's say you want to create a fish, then use its ID in a subsequent request. Then you want chains.

profiles:
  local:
    data:
      host: http://localhost:5000

chains:
  fish_id:
    source: !request create_fish
    # This uses JSONPath to get a single value from the response body
    # https://jsonpath.com/
    selector: $.id

requests:
  create_fish:
    method: POST
    url: "{{host}}/fishes"
    body: >
      {"kind": "barracuda", "name": "Jimmy"}

  get_fish:
    method: GET
    url: "{{host}}/fishes/{{chains.fish_id}}"

Now we can make our requests back-to-back:

> slumber request -p local create_fish
# http://localhost:5000/fishes
{"id": 2, "kind": "barracuda", "name": "Jimmy"}
# http://localhost:5000/fishes/2
> slumber request -p local get_fish
{"id": 2, "kind": "barracuda", "name": "Jimmy"}

This demonstrates how to use chains to link responses to requests. Chains can link to other value sources though, including user-provided values (via a prompt) and shell commands. For a full list of chain types, see the Chain API reference.

Recursive Templates

What if you need a more complex chained value? Let's say the endpoint to get a fish requires the fish ID to be in the format fish_{id}. Why? Don't worry about it. Fish are particular. Profile values support recursively templating via the !template tag. You can use this to compose template values into more complex strings.

profiles:
  local:
    data:
      host: http://localhost:5000
      fish_id: !template "fish_{{chains.fish_id}}"

chains:
  fish_id:
    source: !request create_fish
    selector: $.id

requests:
  create_fish:
    method: POST
    url: "{{host}}/fishes"
    body: >
      {"kind": "barracuda", "name": "Jimmy"}

  get_fish:
    method: GET
    url: "{{host}}/fishes/{{fish_id}}"

And let's see it in action:

> slumber request -p local create_fish
# http://localhost:5000/fishes
{"id": 2, "kind": "barracuda", "name": "Jimmy"}
> slumber request -p local get_fish
# http://localhost:5000/fishes/fish_2
{"id": "fish_2", "kind": "barracuda", "name": "Jimmy"}

Collection Reuse & Inheritance

The Problem

Let's start with an example of something that sucks. Let's say you're making requests to a fish-themed JSON API, and it requires authentication. Gotta protect your fish! Your request collection might look like so:

profiles:
  production:
    data:
      host: https://myfishes.fish
      fish_id: 6

chains:
  token:
    source: !file ./api_token.txt

requests:
  list_fish:
    method: GET
    url: "{{host}}/fishes"
    headers:
      Accept: application/json
      Authorization: Bearer {{chains.token}}

  get_fish:
    method: GET
    url: "{{host}}/fishes/{{fish_id}}"
    headers:
      Accept: application/json
      Authorization: Bearer {{chains.token}}

The Solution

You've heard of DRY, so you know this is bad. Every new request recipe requires re-specifying the headers, and if anything about the authorization changes, you have to change it in multiple places.

You can easily re-use components of your collection using YAML's merge feature.

profiles:
  production:
    data:
      host: https://myfishes.fish

chains:
  token:
    source: !file ./api_token.txt

# The name here is arbitrary, pick any name you like
request_base: &request_base
  headers:
    Accept: application/json
    Authorization: Bearer {{chains.auth_token}}

requests:
  list_fish:
    <<: *request_base
    method: GET
    url: "{{host}}/fishes"

  get_fish:
    <<: *request_base
    method: GET
    url: "{{host}}/fishes/{{chains.fish_id}}"

Great! That's so much cleaner. Now each recipe can inherit whatever base properties you want just by including <<: *request_base. This is still a bit repetitive, but it has the advantage of being explicit. You may have some requests that don't want to include those values.

Recursive Inheritance

But wait! What if you have a new request that needs an additional header? Unfortunately, YAML's merge feature does not support recursive merging. If you need to extend the headers map from the base request, you'll need to pull that map in manually:

profiles:
  production:
    data:
      host: https://myfishes.fish

chains:
  token:
    source: !file ./api_token.txt

# The name here is arbitrary, pick any name you like
request_base: &request_base
  headers: &headers_base # This will let us pull in the header map to extend it
    Accept: application/json
    Authorization: Bearer {{chains.auth_token}}

requests:
  list_fish:
    <<: *request_base
    method: GET
    url: "{{host}}/fishes"

  get_fish:
    <<: *request_base
    method: GET
    url: "{{host}}/fishes/{{chains.fish_id}}"

  create_fish:
    # Note: in this case, pulling in request_base doesn't do anything since we
    # then overwite its only field (headers), but this is good practice in case
    # you add an additional field to request_base
    <<: *request_base
    method: POST
    url: "{{host}}/fishes"
    headers:
      <<: *headers_base
      Content-Type: application/json
    body: >
      {"kind": "barracuda", "name": "Jimmy"}

Data Filtering & Querying

Slumber supports querying data structures to transform or reduce response data.

There are two main use cases for querying:

  • In chained template values, to extract data
    • Provided via chain's selector argument
  • In the TUI response body browser, to limit the response data shown

Regardless of data format, querying is done via JSONPath. For non-JSON formats, the data will be converted to JSON, queried, and converted back. This keeps querying simple and uniform across data types.

Querying Chained Values

Here's some examples of using queries to extract data from a chained value. Let's say you have two chained value sources. The first is a JSON file, called creds.json. It has the following contents:

{ "user": "fishman", "pw": "hunter2" }

We'll use these credentials to log in and get an API token, so the second data source is the login response, which looks like so:

{ "token": "abcdef123" }
chains:
  username:
    source: !file ./creds.json
    selector: $.user
  password:
    source: !file ./creds.json
    selector: $.pw
  auth_token:
    source: !request login
    selector: $.token

# Use YAML anchors for de-duplication
base: &base
  headers:
    Accept: application/json
    Content-Type: application/json

requests:
  login:
    <<: *base
    method: POST
    url: "https://myfishes.fish/anything/login"
    body: |
      {
        "username": "{{chains.username}}",
        "password": "{{chains.password}}"
      }

  get_user:
    <<: *base
    method: GET
    url: "https://myfishes.fish/anything/current-user"
    query:
      auth: "{{chains.auth_token}}"

While this example simple extracts inner fields, JSONPath can be used for much more powerful transformations. See the JSONPath docs or this JSONPath editor for more examples.

Configuration

Configuration provides application-level settings, as opposed to collection-level settings.

Location & Creation

Configuration is stored in the Slumber root directory, under the file config.yml. To find the root directory, you can run:

slumber show dir

To quickly create and edit the file:

# Replace vim with your favorite text editor
vim $(slumber show dir)/config.yml

If the root directory doesn't exist yet, you can create it yourself or have Slumber create it by simply starting the TUI.

Fields

FieldTypeDescriptionDefault
preview_templatesbooleanRender template values in the TUI? If false, the raw template will be shown.true

Request Collection

The request collection is the primary configuration for Slumber. It defines which requests can be made, and how to make them. When running a slumber instance, a single collection file is loaded. If you want to work with multiple collections at once, you'll have to run multiple instances of Slumber.

Collection files are designed to be sharable, meaning you can commit them to your Git repo. The most common pattern is to create one collection per API repo, and check it into the repo so other developers of the API can use the same collection. This makes it easy for any new developer or user to learn how to use an API.

Format & Loading

A collection is defined as a YAML file. When you run slumber, it will search the current directory for the following default collection files, in order:

  • slumber.yml
  • slumber.yaml
  • .slumber.yml
  • .slumber.yaml

Whichever of those files is found first will be used. If you want to use a different file for your collection (e.g. if you want to store multiple collections in the same directory), you can override the auto-search with the --collection (or -c) command line argument. E.g.:

slumber -c my-collection.yml

Collection History & Migration

Each collection needs a unique ID generated when the collection is first loaded by Slumber. This ID is used to persist request history and other data related to the collection. If you move a collection file, a new ID will be generated and it will be unlinked from its previous history. If you want to retain that history, you can migrate data from the old ID to the new one like so:

slumber collections migrate /slumber/old.yml /slumber/new.yml

If you don't remember the path of the old file, you can list all known collections with:

slumber collections list

Fields

A request collection supports the following top-level fields:

FieldTypeDescriptionDefault
profilesmapping[string, Profile]Static template values[]
requestsmapping[string, RequestRecipe]Requests Slumber can send[]
chainsmapping[string, Chain]Complex template values[]

Examples

profiles:
  local:
    name: Local
    data:
      host: http://localhost:5000
      user_guid: abc123
  prd:
    name: Production
    data:
      host: https://httpbin.org
      user_guid: abc123

chains:
  username:
    source: !file ./username.txt
  password:
    source: !prompt Password
    sensitive: true
  auth_token:
    source: !request login
    selector: $.token

# Use YAML anchors for de-duplication
base: &base
  headers:
    Accept: application/json
    Content-Type: application/json

requests:
  login:
    <<: *base
    method: POST
    url: "{{host}}/anything/login"
    body: |
      {
        "username": "{{chains.username}}",
        "password": "{{chains.password}}"
      }

  get_user:
    <<: *base
    method: GET
    url: "{{host}}/anything/current-user"
    query:
      auth: "{{chains.auth_token}}"

Profile

A profile is a collection of static template values. It's useful for configuring and switching between multiple different environments/settings/etc.

Profiles also support nested templates, via the !template tag.

Fields

FieldTypeDescriptionDefault
namestringDescriptive name to use in the UIValue of key in parent
datamapping[string, ProfileValue]Fields, mapped to their values{}

Examples

local:
  name: Local
  data:
    host: localhost:5000
    url: !template "https://{{host}}"
    user_guid: abc123

Profile Value

A profile value is the value associated with a particular key in a profile. Typically profile values are just simple strings, but they can also be other variants.

In the case of a nested template, the inner template will be rendered into its own value, then injected into the outer string.

Variants

VariantTypeDescription
rawstringStatic string (key can optionally be omitted)
templateTemplateNested template, to be rendered inline

Examples

# The !raw key is the default, and can be omitted
http://localhost:5000
---
!raw http://localhost:5000
---
!template http://{{hostname}}

Request Recipe

A request recipe defines how to make a particular request. For a REST API, you'll typically create one request recipe per endpoint.

Fields

FieldTypeDescriptionDefault
namestringDescriptive name to use in the UIValue of key in parent
methodstringHTTP request methodRequired
urlTemplateHTTP request URLRequired
querymapping[string, Template]HTTP request query parameters{}
headersmapping[string, Template]HTTP request headers{}
bodyTemplateHTTP request bodynull

Examples

login:
  name: Login
  method: POST
  url: "{{host}}/anything/login"
  headers:
    accept: application/json
    content-type: application/json
  query:
    root_access: yes_please
  body: |
    {
      "username": "{{chains.username}}",
      "password": "{{chains.password}}"
    }

Chain

A chain is a intermediate data type to enable complex template values. Chains also provide additional customization, such as marking values as sensitive.

To use a chain in a template, reference it as {{chains.<id>}}.

Fields

FieldTypeDescriptionDefault
sourceChainSourceSource of the chained valueRequired
sensitivebooleanShould the value be hidden in the UI?false
selectorJSONPathSelector to transform/narrow down results in a chained value. See Filtering & Queryingnull
content_typeContentTypeForce content type. Not required for request and file chains, as long as the Content-Type header/file extension matches the data

See the ChainSource docs for more detail.

Examples

# Load chained value from a file
username:
  source: !file ./username.txt
---
# Prompt the user for a value whenever the request is made
password:
  source: !prompt Enter Password
  sensitive: true
---
# Use a value from another response
# Assume the request recipe with ID `login` returns a body like `{"token": "foo"}`
auth_token:
  source: !request login
  selector: $.token

Chain Source

A chain source defines how a Chain gets its value. It populates the source field of a chain. There are multiple source types, and the type is specified using YAML's tag syntax.

Variants

VariantTypeValueChained Value
requeststringRequest Recipe IDBody of the most recent response for a specific request recipe.
commandstring[][program, ...arguments]Stdout of the executed command
filestringPath (relative to current directory)Contents of the file
promptstringDescriptive prompt for the userValue entered by the user

Examples

See the Chain docs for more holistic examples.

!request login
---
!command ["echo", "-n", "hello"]
---
!file ./username.txt
---
!prompt Enter Password

Template

A template is represented in YAML as a normal string, and thus supports all of YAML's string syntaxes. Templates receive post-processing that injects dynamic values into the string. A templated value is represented with {{...}}.

Templates can generally be used in any value in a request recipe (not in keys). They cannot be used in profiles or chains, to avoid the potential for recursive templating. If this feature would be useful to you, le tme know.

Template Sources

There are several ways of sourcing templating values:

SourceSyntaxDescription
Profile Field{{field_name}}Static value from a profile
Environment Variable{{env.VARIABLE}}Environment variable from parent shell/process
Chain{{chains.chain_id}}Complex chained value

Examples

# Profile value
"hello, {{location}}"
---
# Multiple dynamic values
"{{greeting}}, {{location}}"
---
# Environment variable
"hello, {{env.LOCATION}}"
---
# Chained value
"hello, {{chains.where_am_i}}"
---
# No dynamic values
"hello, world!"

Content Type

Content type defines the various data formats that Slumber recognizes and can manipulate. Slumber is capable of displaying any text-based data format, but only specific formats support additional features such as querying and formatting.

For chained requests, Slumber uses the HTTP Content-Type header to detect the content type. For chained files, it uses the file extension. For other chain sources, or if the Content-Type header/file extension is missing or incorrect, you'll have to manually provide the content type via the chain content_type field.

Supported Content Types

Content TypeHTTP HeaderFile Extension(s)
JSONapplication/jsonjson