Skip to content

Bell Syntax Guide

The Bell language is designed to be as close to plain English as possible for making HTTP requests.

Variables and String Interpolation

Assign values to variables with =. Use {variableName} to interpolate a variable into a string.

bel
# Assign a string to a variable
apiKey = "my-secret-token"
userId = 42

# Use variables inside strings with {variableName}
url "https://api.example.com/users/{userId}"
header "Authorization" "Bearer {apiKey}"

GET

Request Building

Use these keywords to build up your HTTP request before sending it.

url

Sets the full base URL for the request. When an environment is active, path should be used instead and the base URL comes from the environment config.

path

Appends a path segment to the base URL.

bel
# url sets the full base URL for the request
url "https://api.example.com"

# path appends a segment to the base URL
# result: https://api.example.com/users/123
path "/users/123"

GET

param

Adds query parameters to the URL.

bel
url "https://api.example.com/restaurants/search"

# param adds a query parameter: ?page=2&query=Sushi&location=NY
param "page"     2
param "query"    "Sushi"
param "location" "NY"

# param can also use a variable directly: ?userId=<value>
userId = "abc123"
param userId

GET

header and headers

Adds HTTP headers to the request. Use header for individual headers or headers to set them all at once with an object.

bel
token = "my-token"

url "https://api.example.com/data"

# Add individual headers
header "Authorization" "Bearer {token}"
header "Accept"        "application/json"

# Or set all headers at once with an object
headers {
  "Authorization": "Bearer {token}",
  "Accept": "application/json"
}

GET

body

Sets the request body. Typically used with POST, PUT, and PATCH.

bel
url "https://api.example.com/users"

body {
  "name": "Jane Doe",
  "email": "jane@example.com",
  "role": "admin"
}

POST

Sending a Request

Writing an HTTP method keyword dispatches the currently built request.

bel
url "https://api.example.com/users"

# GET — retrieve a resource
GET

# POST — create a resource
body { "name": "Jane" }
POST

# PUT — replace a resource
path "/users/1"
body { "name": "Jane Doe" }
PUT

# PATCH — update fields on a resource
path "/users/1"
body { "name": "Jane D." }
PATCH

# DELETE — remove a resource
path "/users/1"
DELETE

Logging

Use log to print a value to the console during execution. You can log any expression, including response data, variables, and the current URL.

bel
url "https://api.example.com/users/1"
GET

# Log a value or expression
log response.status
log response.body.name

# Log a variable
count = 42
log count

# Log the current URL
log url

Response

After a request is dispatched, the response object is set. Bell keeps a history of all responses made in a file.

bel
url "https://api.example.com/users/1"
GET

# Access the full response body
log response.body

# Reach into nested properties
log response.body.name
log response.body.address.city

# Access the HTTP status code
log response.status

# Access response headers
log response.headers

# When multiple requests are made, index into past responses
url "https://api.example.com/login"
POST

url "https://api.example.com/data"
GET

# response[0] is the login response, response[1] is the data response
log response[0].status
log response[1].body

User Interaction

input

Prompts the user for a value at runtime. The result can be assigned to a variable or used inline.

bel
url "https://api.example.com/users"

# input() prompts the user for a value at runtime
id = input("Enter user ID")
path "/{id}"

GET

# input() can also be used inline in body or headers
url "https://api.example.com/posts"
body {
  "title": input("Post title"),
  "body":  input("Post body")
}
POST

warn

Displays a confirmation prompt before continuing. As an expression, it returns the value if confirmed. As a statement, it shows the message and waits for confirmation.

bel
# warn as an expression — prompts for confirmation and returns the value
# execution stops if the user declines
prodUrl = warn "production.example.com"

url "https://{prodUrl}/users"
GET

# warn as a statement — shows a message and asks the user to confirm
warn "You are about to delete a user. Proceed?"

url "https://api.example.com/users/1"
DELETE

Testing

expect

Asserts that an expression is truthy. If the assertion fails, the file exits with an error. Use Bell files as lightweight API test suites.

bel
url "https://api.example.com/users/1"
GET

# expect asserts that an expression is truthy
# the file exits with an error if it is not
expect response.status === 200
expect response.body.name === "Jane Doe"

url "https://api.example.com/users"
body { "name": "New User" }
POST

expect response.status === 201

validate

Validates that a value matches a TypeScript type at runtime. Useful for verifying request and response shapes stay in sync with your type definitions.

bel
import requestBody from "./request-body.json"
import { UserRequest, UserResponse } from "./types.ts"

# validate checks that a value matches a TypeScript type at runtime
validate requestBody as UserRequest

url "https://api.example.com/users"
body requestBody
POST

validate response.body as UserResponse

Imports

Bell supports three import forms.

bel
# Import a default export from a JSON file
import requestBody from "./request-body.json"

# Import named exports from a TypeScript file
import { UserRequest, UserResponse } from "./types.ts"

# Import a JSON environment config file (anonymous import)
# This loads environment definitions (dev, prod, etc.)
import "./env-config.json"

.env Files

Bell can load standard .env files directly. All three import forms work with .env files (detected by filename, not extension).

bel
# Anonymous import — all KEY=VALUE pairs become Bell variables.
# BELL_URL sets the base URL so path statements work without an explicit url call.
import ".env"

header "Authorization" "Bearer {API_KEY}"
path "/users/{USER_ID}"
GET

# Named import — pull specific keys from the .env file
import { API_KEY, USER_ID } from ".env"

# Default import — get all vars as a single object
import env from ".env"
log env.API_KEY
env
BELL_URL=https://api.example.com
API_KEY=my-secret-token
USER_ID=42

The special key BELL_URL sets the base URL so that path statements work without an explicit url call — the same role the "url" key plays in a JSON environment config.

Bell prints a warning if the .env file is not found, since it is commonly gitignored.

Environments

Environments let you switch between configurations (dev, staging, prod) without changing your Bell files. Define them in a JSON config and load it with an anonymous import.

The config JSON maps environment names to objects. The url key sets the base URL; any other keys become variables.

json
{
  "dev": {
    "url": "https://dev.api.example.com",
    "apiKey": "dev-key-123"
  },
  "prod": {
    "url": "https://api.example.com",
    "apiKey": "prod-key-456"
  }
}

Use the env keyword to select an environment before running requests.

bel
import "./env-config.json"

# env with no argument prompts the user to choose from all available environments
env

# env with options limits the choices shown to the user
env "dev" | "prod"

# env with a single value sets the environment without prompting
env "prod"

# after env is set, the url and any env variables are available
path "/users"
GET

Multi-file Workflows

request

Runs another Bell file inline. Any variables the sub-file exports become available in the calling file.

bel
# request runs another Bell file inline
# exported variables from that file become available here
request "./request-sub.bel"

# token is now available from the sub-file's export
url "https://api.example.com/posts"
header "Authorization" "Bearer {token}"
GET
bel
url "https://api.example.com/login"

body {
  "username": "testuser",
  "password": "secret"
}

POST

token = response.body.token
export token

export

Marks variables to be shared with any file that requests this one.

bel
url "https://api.example.com/login"

body {
  "username": "testuser",
  "password": "secret"
}

POST

# Save the token from the response
token = response.body.token
userId = response.body.userId

# export makes these variables available to files that request this one
export token, userId

Keywords as Identifiers

Bell keywords like url, body, and headers can be used as variable names in expressions — for example, to log the current URL or read fields from the response body.

bel
log url
log body.data.entry