A powerful CLI tool for HTTP API testing and automation
HttpCraft is a command-line interface (CLI) tool designed to simplify testing and interacting with HTTP/S endpoints. It provides a highly ergonomic, configurable, and extensible experience for developers, QA engineers, and DevOps professionals who need to make HTTP requests frequently.
- 🔧 Configuration-driven: Define APIs, endpoints, and workflows in YAML files
- 🔄 Variable substitution: Dynamic variables with multiple scopes and precedence rules
- 📝 Profiles: Switch between different environments (dev, staging, prod) or user contexts
- 🔗 Request chaining: Execute sequences of requests with data passing between steps
- 🧩 Plugin system: Extend functionality with custom JavaScript/TypeScript plugins
- ⚡ Tab completion: Full ZSH completion for commands, API names, and options
- 🔍 Verbose output: Detailed request/response information for debugging
- 🏃 Dry run mode: Preview requests without sending them
- 🔐 Secret management: Secure handling of API keys and tokens
- 🎯 Exit code control: Scriptable with controllable exit codes
- 🔐 OAuth2 Authentication: Built-in support for multiple OAuth2 flows
# Clone the repository
git clone <repository-url>
cd httpcraft
# Install dependencies
npm install
# Build the project
npm run build
# Link globally (optional)
npm link
# Run directly with ts-node
npm run dev <command>
Create .httpcraft.yaml
in your current directory:
# .httpcraft.yaml
apis:
jsonplaceholder:
baseUrl: "https://jsonplaceholder.typicode.com"
endpoints:
getTodo:
description: "Fetches a single todo item."
method: GET
path: "/todos/1"
createPost:
description: "Creates a new post."
method: POST
path: "/posts"
headers:
Content-Type: "application/json; charset=UTF-8"
body:
title: "My New Post"
body: "This is the post content."
userId: 1
# Fetch a todo item
httpcraft jsonplaceholder getTodo
# Create a new post
httpcraft jsonplaceholder createPost
profiles:
dev:
baseUrl: "https://api-dev.example.com"
apiKey: "dev-key-123"
prod:
baseUrl: "https://api.example.com"
apiKey: "{{secret.PROD_API_KEY}}"
apis:
myapi:
baseUrl: "{{profile.baseUrl}}"
headers:
Authorization: "Bearer {{profile.apiKey}}"
endpoints:
getUser:
method: GET
path: "/users/{{userId}}"
# Use different profiles
httpcraft --profile dev myapi getUser --var userId=123
httpcraft --profile prod myapi getUser --var userId=123
# Make a simple HTTP request
httpcraft request <url>
# Call a configured API endpoint
httpcraft <api_name> <endpoint_name>
# Execute a request chain
httpcraft chain <chain_name>
# Generate ZSH completion script
httpcraft completion zsh
Option | Description |
---|---|
--config, -c |
Path to configuration file |
--var |
Set or override variables (can be used multiple times) |
--profile, -p |
Select profile(s) to use (can be used multiple times) |
--no-default-profile |
Skip default profiles and use only CLI-specified profiles |
--verbose |
Output detailed request/response information to stderr |
--dry-run |
Preview request without sending it |
--exit-on-http-error |
Exit with non-zero code for specified HTTP errors |
--chain-output |
Output format for chains ("default" or "full") |
# Basic API call
httpcraft myapi getUser
# With variables
httpcraft myapi getUser --var userId=123 --var format=json
# With profiles
httpcraft --profile prod myapi getUser --var userId=123
# Enhanced profile merging (combines default + CLI profiles)
httpcraft --profile user-alice myapi getUser
# Override default profiles (use only CLI profiles)
httpcraft --no-default-profile --profile user-alice myapi getUser
# Verbose output
httpcraft --verbose myapi getUser --var userId=123
# Dry run (preview without sending)
httpcraft --dry-run myapi getUser --var userId=123
# Exit on HTTP errors
httpcraft --exit-on-http-error "4xx,5xx" myapi getUser --var userId=123
# Execute a chain
httpcraft chain createAndGetUser --var userName="John Doe"
# Chain with full output
httpcraft chain createAndGetUser --chain-output full
HttpCraft now supports additive profile merging, making it easier to work with layered configurations:
When you specify profiles via --profile
, they are combined with your config.defaultProfile
:
# .httpcraft.yaml
config:
defaultProfile: ["base", "dev"] # Base environment setup
profiles:
base:
apiUrl: "https://api.example.com"
timeout: 30
de
CEB7
v:
environment: "development"
debug: true
user-alice:
userId: "alice123"
apiKey: "alice-key"
# This combines ALL profiles: base + dev + user-alice
httpcraft --profile user-alice myapi getUser
# Equivalent to the old behavior:
# httpcraft --profile base --profile dev --profile user-alice myapi getUser
Use --no-default-profile
when you want only the CLI-specified profiles:
# Uses ONLY user-alice profile (skips base + dev)
httpcraft --no-default-profile --profile user-alice myapi getUser
Variables from later profiles override earlier ones:
- Default profiles (in order specified)
- CLI profiles (in order specified)
# Order: base → dev → user-alice → admin
httpcraft --profile user-alice --profile admin myapi getUser
See exactly which profiles are being merged:
httpcraft --verbose --profile user-alice myapi getUser
Output:
[VERBOSE] Loading profiles:
[VERBOSE] Default profiles: base, dev
[VERBOSE] CLI profiles: user-alice
[VERBOSE] Final profile order: base, dev, user-alice
[VERBOSE] Merged profile variables:
[VERBOSE] apiUrl: https://api.example.com (from base profile)
[VERBOSE] environment: development (from dev profile)
[VERBOSE] userId: alice123 (from user-alice profile)
No breaking changes - existing configurations work unchanged:
- If you don't use
config.defaultProfile
, behavior is identical - If you do use
config.defaultProfile
, you get the improved UX automatically - Use
--no-default-profile
to restore old behavior if needed
Environment + User Pattern:
config:
defaultProfile: ["base", "production"]
profiles:
base:
apiUrl: "https://api.example.com"
timeout: 30
production:
environment: "prod"
logLevel: "warn"
user-alice:
userId: "alice"
apiKey: "{{secret.ALICE_API_KEY}}"
# Production environment with Alice's credentials
httpcraft --profile user-alice myapi getUser
Team + Environment Pattern:
config:
defaultProfile: "team-defaults"
profiles:
team-defaults:
apiUrl: "https://api.company.com"
userAgent: "CompanyTool/1.0"
staging:
environment: "staging"
apiUrl: "https://staging-api.company.com"
production:
environment: "production"
# Team defaults + staging environment
httpcraft --profile staging myapi getUser
# Only staging (no team defaults)
httpcraft --no-default-profile --profile staging myapi getUser
HttpCraft searches for configuration files in this order:
- File specified via
--config <path>
(highest priority) .httpcraft.yaml
or.httpcraft.yml
in current directory$HOME/.config/httpcraft/config.yaml
(global default)
# Global configuration
config:
defaultProfile: "dev"
# Variable profiles
profiles:
dev:
baseUrl: "https://api-dev.example.com"
apiKey: "dev-key"
prod:
baseUrl: "https://api.example.com"
apiKey: "{{secret.PROD_API_KEY}}"
# Global variable files
variables:
- "./vars/global.yaml"
# Secret configuration
secrets:
provider: "environment" # Default: OS environment variables
# Plugin configuration
plugins:
- path: "./plugins/auth-plugin.js"
config:
authEndpoint: "https://auth.example.com"
# API definitions
apis:
myapi:
baseUrl: "{{profile.baseUrl}}"
headers:
Authorization: "Bearer {{profile.apiKey}}"
variables:
version: "v1"
endpoints:
getUser:
method: GET
path: "/{{api.version}}/users/{{userId}}"
variables:
format: "json"
# Request chains
chains:
userWorkflow:
description: "Create user and fetch profile"
vars:
userName: "Default User"
steps:
- id: createUser
call: myapi.createUser
with:
body:
name: "{{userName}}"
- id: getUser
call: myapi.getUser
with:
pathParams:
userId: "{{steps.createUser.response.body.id}}"
Variables use {{variable_name}}
syntax and follow this precedence (highest to lowest):
- CLI arguments (
--var key=value
) - Step
with
overrides (in chains) - Chain variables (
chain.vars
) - Endpoint variables
- API variables
- Profile variables
- Global variable files
- Secret variables (
{{secret.KEY}}
) - Environment variables (
{{env.VAR}}
) - Plugin variables (
{{plugins.name.var}}
) - Dynamic variables (
{{$timestamp}}
,{{$randomInt}}
, etc.)
{{$timestamp}}
- Unix timestamp{{$isoTimestamp}}
- ISO 8601 timestamp{{$randomInt}}
- Random integer{{$guid}}
- UUID v4
Chains allow you to execute sequences of requests with data passing:
chains:
userOnboarding:
description: "Complete user registration flow"
vars:
email: "user@example.com"
steps:
- id: register
call: auth.register
with:
body:
email: "{{email}}"
password: "temppass123"
- id: verify
call: auth.verify
with:
body:
token: "{{steps.register.response.body.verificationToken}}"
- id: getProfile
call: users.getProfile
with:
pathParams:
userId: "{{steps.register.response.body.userId}}"
Access step data using:
{{steps.stepId.response.body.field}}
- Response body data{{steps.stepId.response.headers['Header-Name']}}
- Response headers{{steps.stepId.response.status}}
- Response status code{{steps.stepId.request.url}}
- Request URL{{steps.stepId.request.body.field}}
- Request body data
HttpCraft supports an extensible plugin system with two approaches for plugin definitions:
- Global plugins - Defined once and reused across multiple APIs
- Inline plugins - Defined directly within API configurations for API-specific functionality
Define plugins once in the global plugins
section for reuse across multiple APIs:
# Global plugin definitions
plugins:
- name: "sharedAuth"
path: "./plugins/shared-auth.js"
config:
baseUrl: "https://auth.example.com"
timeout: 30000
- name: "logging"
npmPackage: "httpcraft-logging-plugin"
config:
level: "info"
apis:
userAPI:
baseUrl: "https://api.example.com"
plugins:
# Reference global plugins with API-specific overrides
- name: "sharedAuth"
config:
scope: "user:read user:write"
- name: "logging"
paymentAPI:
baseUrl: "https://payments.example.com"
plugins:
# Same global plugins, different configuration
- name: "sharedAuth"
config:
scope: "payment:read payment:write"
- name: "logging"
config:
level: "debug" # Override global config
Define plugins directly within API configurations for API-specific functionality:
apis:
userAPI:
baseUrl: "https://api.example.com"
plugins:
# Inline plugin with local file
- name: "userValidator"
path: "./plugins/user-validator.js"
config:
strictMode: true
requiredFields: ["email", "username"]
# Inline plugin with npm package
- name: "userMetrics"
npmPackage: "@company/user-metrics-plugin"
config:
trackingId: "user-api-v1"
paymentAPI:
baseUrl: "https://payments.example.com"
plugins:
# Different inline plugins for payment API
- name: "fraudDetection"
path: "./plugins/fraud-detection.js"
config:
threshold: 0.85
- name: "stripeIntegration"
npmPackage: "stripe-httpcraft-plugin"
config:
apiVersion: "2023-10-16"
webhookSecret: "{{secret.STRIPE_WEBHOOK_SECRET}}"
Combine global and inline plugins for maximum flexibility:
# Global plugins for common functionality
plugins:
- name: "oauth2"
config:
clientId: "{{env.OAUTH2_CLIENT_ID}}"
clientSecret: "{{secret.OAUTH2_CLIENT_SECRET}}"
tokenUrl: "https://auth.example.com/oauth2/token"
apis:
userAPI:
baseUrl: "https://api.example.com"
plugins:
# Reference built-in global plugin
- name: "oauth2"
config:
scope: "user:read user:write"
# API-specific inline plugin
- name: "userAudit"
path: "./plugins/user-audit.js"
config:
auditLevel: "detailed"
logDestination: "user-api-logs"
notificationAPI:
baseUrl: "https://notifications.example.com"
plugins:
# Only inline plugins (no global dependencies)
- name: "twilioSMS"
npmPackage: "@httpcraft/twilio-plugin"
config:
accountSid: "{{secret.TWILIO_ACCOUNT_SID}}"
authToken: "{{secret.TWILIO_AUTH_TOKEN}}"
// plugins/customAuth.js
export default {
async setup(context) {
// Pre-request hook
context.registerPreRequestHook(async (request) => {
const token = await getApiToken(context.config.apiKey);
request.headers['Authorization'] = `Bearer ${token}`;
});
// Custom variables
context.registerVariableSource('apiToken', async () => {
return await getApiToken(context.config.apiKey);
});
// Parameterized functions
context.registerParameterizedVariableSource('getTokenWithScope', async (scope) => {
return await getApiToken(context.config.apiKey, scope);
});
// Post-response hook
context.registerPostResponseHook(async (request, response) => {
if (response.headers['content-type']?.includes('xml')) {
response.body = convertXmlToJson(response.body);
3D11
}
});
}
};
Global Plugins:
- ✅ Reusability - Define once, use everywhere
- ✅ Consistency - Same plugin version across APIs
- ✅ Maintenance - Single place for updates
- ✅ Configuration sharing - Base config with API overrides
Inline Plugins:
- ✅ Simplicity - No global definition required
- ✅ API-specific - Tailored to specific API needs
- ✅ Reduced ceremony - Direct definition where needed
- ✅ Experimentation - Easy to test one-off plugins
Best Practices:
- Use global plugins for authentication, logging, and shared functionality
- Use inline plugins for API-specific validation, transformations, and integrations
- Combine both approaches in complex applications for optimal flexibility
Enable intelligent tab completion for faster workflows:
# Quick setup - add to your ~/.zshrc
eval "$(httpcraft completion zsh)"
# Or generate completion script manually
httpcraft completion zsh > ~/.zsh/completions/_httpcraft
# Add to .zshrc
echo 'fpath=(~/.zsh/completions $fpath)' >> ~/.zshrc
echo 'autoload -Uz compinit && compinit' >> ~/.zshrc
# Reload shell
source ~/.zshrc
If you encounter _arguments:comparguments:327: can only be called from completion function
, ensure you're using the latest version - this was fixed to properly use compdef
for completion setup.
Note: The completion script automatically initializes the ZSH completion system (compinit
) if needed, so no manual setup is required.
httpcraft <TAB> # Complete API names
httpcraft myApi <TAB> # Complete endpoint names
httpcraft --profile <TAB> # Complete profile names
httpcraft chain <TAB> # Complete chain names
# Test a REST API
httpcraft jsonplaceholder getTodo
httpcraft jsonplaceholder createPost --var title="Test Post"
# Test against different environments
httpcraft --profile dev myapi getUser --var userId=123
httpcraft --profile staging myapi getUser --var userId=123
httpcraft --profile prod myapi getUser --var userId=123
# Execute multi-step workflow
httpcraft chain userOnboarding --var email="test@example.com"
# Debug workflow with verbose output
httpcraft --verbose chain userOnboarding --var email="test@example.com"
# Get detailed chain output
httpcraft chain userOnboarding --chain-output full
#!/bin/bash
# deployment-test.sh
# Test API endpoints after deployment
httpcraft --profile prod --exit-on-http-error "4xx,5xx" api healthCheck
httpcraft --profile prod --exit-on-http-error "4xx,5xx" api getVersion
# Run integration test chain
httpcraft --profile prod chain integrationTest --var testId="deploy-$(date +%s)"
echo "Deployment tests completed successfully!"
HttpCraft includes a comprehensive JSON Schema for configuration validation and editor integration.
- ✅ Validation: Catch configuration errors before runtime
- ✅ Autocompletion: Get intelligent suggestions in your editor
- ✅ Documentation: Hover help for all configuration options
- ✅ Type checking: Ensure correct data types and formats
Install the "YAML" extension by Red Hat, then add to your VS Code settings:
{
"yaml.schemas": {
"./schemas/httpcraft-config.schema.json": [
".httpcraft.yaml",
".httpcraft.yml",
"**/httpcraft.yaml",
"**/httpcraft.yml"
]
}
}
Or add a schema reference directly to your config file:
# yaml-language-server: $schema=./schemas/httpcraft-config.schema.json
apis:
myapi:
baseUrl: "https://api.example.com"
# Editor will provide autocompletion and validation here!
- Go to Settings → Languages & Frameworks → Schemas and DTDs → JSON Schema Mappings
- Click + to add a new mapping
- Set Schema file or URL to
./schemas/httpcraft-config.schema.json
- Add file patterns:
*.httpcraft.yaml
,*.httpcraft.yml
Validate your configuration files using ajv-cli
:
# Install ajv-cli globally
npm install -g ajv-cli
# Validate your config
ajv validate -s schemas/httpcraft-config.schema.json -d .httpcraft.yaml
The schema enforces:
- Required properties:
apis
at root,baseUrl
/endpoints
for APIs - HTTP methods: Only valid methods (
GET
,POST
,PUT
, etc.) - URL formats: Base URLs must be HTTP/HTTPS or contain variables
- Naming conventions: API/endpoint names must follow identifier patterns
- Variable syntax: Supports
{{variable}}
substitutions - Chain structure: Proper step definitions with
id
andcall
# This configuration is fully validated by the schema
config:
defaultProfile: "development"
profiles:
development:
baseUrl: "https://jsonplaceholder.typicode.com"
apiKey: "dev-key-123"
apis:
jsonplaceholder:
baseUrl: "{{profile.baseUrl}}"
headers:
Authorization: "Bearer {{profile.apiKey}}"
endpoints:
getTodo:
method: GET
path: "/todos/{{todoId}}"
variables:
todoId: 1
chains:
testFlow:
steps:
- id: getTodo
call: jsonplaceholder.getTodo
src/
├── cli/ # CLI command handlers
├── core/ # Core HTTP and configuration logic
└── types/ # TypeScript type definitions
tests/
├── unit/ # Unit tests
└── integration/ # Integration tests
examples/ # Example configurations
└── plugins/ # Example plugins
# Install dependencies
npm install
# Build the project
npm run build
# Run tests
npm test
# Run linting
npm run lint
# Format code
npm run format
This project includes Nix flake support for reproducible development environments:
# Enter development shell
nix develop
# Or use direnv for automatic activation
echo "use flake" > .envrc
direnv allow
For detailed Nix setup and usage instructions, see the Nix Usage Guide.
Configuration file not found:
# Check config file search paths
httpcraft --config ./my-config.yaml myapi endpoint
Variable resolution errors:
# Use dry-run to debug variable resolution
httpcraft --dry-run myapi endpoint --var debug=true
Plugin loading issues:
# Check plugin syntax and exports
node -c ./plugins/my-plugin.js
Completion not working:
# Verify completion script generation
httpcraft completion zsh
# Test completion commands
httpcraft --get-api-names
- Use
httpcraft --help
for command help - Use
httpcraft <command> --help
for command-specific help - Check the
examples/
directory for configuration examples - Enable
--verbose
for detailed request/response information
[License information here]
[Contributing guidelines here]
HttpCraft includes built-in OAuth2 authentication support that works seamlessly with the plugin system:
The OAuth2 plugin is included with HttpCraft - no need to install or reference external files:
plugins:
- name: "oauth2"
config:
grantType: "client_credentials"
tokenUrl: "https://auth.example.com/oauth2/token"
clientId: "{{env.OAUTH2_CLIENT_ID}}"
clientSecret: "{{secret.OAUTH2_CLIENT_SECRET}}"
scope: "api:read api:write"
authMethod: "basic"
apis:
protectedApi:
baseUrl: "https://api.example.com/v1"
endpoints:
getUsers:
method: GET
path: "/users"
# Authorization header automatically added by OAuth2 plugin
- Client Credentials: Server-to-server authentication
- Authorization Code: User authentication with PKCE support
- Refresh Token: Automatic token renewal
Ready-to-use configurations for major providers:
plugins:
- name: "oauth2"
config:
grantType: "client_credentials"
tokenUrl: "https://{{env.AUTH0_DOMAIN}}/oauth/token"
clientId: "{{env.AUTH0_CLIENT_ID}}"
clientSecret: "{{secret.AUTH0_CLIENT_SECRET}}"
scope: "read:users write:users"
authMethod: "post"
plugins:
- name: "oauth2"
config:
grantType: "client_credentials"
tokenUrl: "https://login.microsoftonline.com/{{env.AZURE_TENANT_ID}}/oauth2/v2.0/token"
clientId: "{{env.AZURE_CLIENT_ID}}"
clientSecret: "{{secret.AZURE_CLIENT_SECRET}}"
scope: "https://graph.microsoft.com/.default"
plugins:
- name: "oauth2"
config:
grantType: "client_credentials"
tokenUrl: "https://oauth2.googleapis.com/token"
clientId: "{{env.GOOGLE_CLIENT_ID}}"
clientSecret: "{{secret.GOOGLE_CLIENT_SECRET}}"
scope: "https://www.googleapis.com/auth/cloud-platform"
Access tokens directly in your configurations:
apis:
manualApi:
baseUrl: "https://api.example.com"
endpoints:
getData:
method: GET
path: "/data"
headers:
# Use plugin variables directly
Authorization: "{{plugins.oauth2.tokenType}} {{plugins.oauth2.accessToken}}"
getAdminData:
method: GET
path: "/admin"
headers:
# Use parameterized functions for custom scopes
Authorization: "Bearer {{plugins.oauth2.getTokenWithScope('admin:read')}}"
- Automatic Token Management: Intelligent caching and renewal
- Custom Cache Keys: Manual cache key specification for multi-user and multi-tenant scenarios
- Provider Support: Works with any OAuth2-compliant provider
- Security: Token masking in logs and dry-run output
- Flexibility: API-level configuration overrides
- Integration: Seamless integration with profiles, variables, and chains
See examples/features/oauth2/builtin_oauth2.yaml
for a complete working example.
HttpCraft supports modern browser-based OAuth2 authentication similar to Insomnia, automatically opening your browser for user authentication:
plugins:
- name: "oauth2"
config:
# Interactive Authorization Code Grant Flow
grantType: "authorization_code"
# OAuth2 Provider Configuration
authorizationUrl: "https://auth.example.com/oauth2/authorize"
tokenUrl: "https://auth.example.com/oauth2/token"
clientId: "{{env.OAUTH2_CLIENT_ID}}"
clientSecret: "{{env.OAUTH2_CLIENT_SECRET}}"
# Scopes and Audience
scope: "openid profile email api:read api:write"
audience: "https://api.example.com"
# Interactive Flow Options (all optional - auto-detected)
# interactive: true # Auto-detected when conditions are met
# usePKCE: true # Enabled by default for security
# codeChallengeMethod: "S256" # Default PKCE method
# tokenStorage: "keychain" # Auto-detected: keychain → filesystem → memory
# callbackPort: 8080 # Auto-selected if not specified
# callbackPath: "/callback" # Default callback path
apis:
userApi:
baseUrl: "https://api.example.com"
endpoints:
getProfile:
path: "/user/profile"
method: GET
# Authorization header automatically added by OAuth2 plugin
First-time authentication:
$ httpcraft userApi getProfile
🔐 Authentication required... # stderr
🌐 Opening browser for OAuth2 authentication... # stderr
⏳ Waiting for authorization (timeout: 5 minutes)... # stderr
✅ Authentication successful! Tokens stored securely. # stderr
{"user": {"id": 123, "name": "John Doe"}} # stdout (for piping)
Subsequent requests:
$ httpcraft userApi getProfile
🔑 Using stored access token # stderr
{"user": {"id": 123, "name": "John Doe"}} # stdout (for piping)
Automatic token refresh:
$ httpcraft userApi getProfile
🔄 Access token expired, refreshing... # stderr
✅ Token refreshed successfully # stderr
{"user": {"id": 123, "name": "John Doe"}} # stdout (for piping)
- Automatic Browser Launch: Opens system browser for authorization
- Secure Token Storage: OS keychain integration with encrypted filesystem fallback
- PKCE Security: Proof Key for Code Exchange enabled by default
- Environment Detection: Graceful degradation in CI/automated environments
- Unix Piping Compatible: Auth messages go to stderr, response to stdout
- Zero Configuration: Interactive mode auto-detected when appropriate
Auth0:
authorizationUrl: "https://your-tenant.auth0.com/authorize"
tokenUrl: "https://your-tenant.auth0.com/oauth/token"
audience: "https://api.your-app.com"
Azure AD:
authorizationUrl: "https://login.microsoftonline.com/{tenant}/oauth2/v2.0/authorize"
tokenUrl: "https://login.microsoftonline.com/{tenant}/oauth2/v2.0/token"
scope: "https://graph.microsoft.com/.default"
Google OAuth2:
authorizationUrl: "https://accounts.google.com/o/oauth2/v2/auth"
tokenUrl: "https://oauth2.googleapis.com/token"
scope: "https://www.googleapis.com/auth/userinfo.profile"
See examples/features/oauth2/interactive_oauth2.yaml
for complete working examples.
Create .httpcraft.yaml
in your project directory:
profiles:
dev:
baseUrl: "https://dev-api.example.com"
prod:
baseUrl: "https://api.example.com"
apis:
jsonplaceholder:
baseUrl: "{{profile.baseUrl}}/v1"
endpoints:
getTodos:
method: GET
path: "/todos"
getTodo:
method: GET
path: "/todos/{{todoId}}"
createTodo:
method: POST
path: "/todos"
headers:
Content-Type: "application/json"
body:
title: "{{title}}"
completed: false
# Basic request
httpcraft jsonplaceholder getTodos
# With variables
httpcraft jsonplaceholder getTodo --var todoId=1
# With profile
httpcraft --profile dev jsonplaceholder getTodos
# Create a todo
httpcraft jsonplaceholder createTodo --var title="Test Todo"
# See request/response details
httpcraft --verbose jsonplaceholder getTodos
# Dry run (see what would be sent)
httpcraft --dry-run jsonplaceholder getTodos
# Global configuration
config:
defaultProfile: "dev"
# Environment profiles
profiles:
dev:
api_base: "https://dev-api.example.com"
debug: true
prod:
api_base: "https://api.example.com"
debug: false
# Global variables
variables:
- "globals.yaml"
# Plugin configuration
plugins:
- path: "./plugins/auth.js"
name: "customAuth"
config:
apiKey: "{{secret.API_KEY}}"
# API definitions
apis:
myService:
baseUrl: "{{profile.api_base}}"
headers:
User-Agent: "HttpCraft/1.0"
variables:
version: "v1"
endpoints:
getUsers:
method: GET
path: "/{{version}}/users"
# Request chains
chains:
userWorkflow:
vars:
email: "test@example.com"
steps:
- id: createUser
call: myService.createUser
- id: getUser
call: myService.getUser
with:
pathParams:
userId: "{{steps.createUser.response.body.id}}"
Chain multiple requests together with data passing:
chains:
userRegistration:
description: "Complete user registration flow"
vars:
email: "user@example.com"
password: "secure123"
steps:
- id: register
call: auth.register
with:
body:
email: "{{email}}"
password: "{{password}}"
- id: login
call: auth.login
with:
body:
email: "{{email}}"
password: "{{password}}"
- id: getProfile
call: users.getProfile
with:
headers:
Authorization: "Bearer {{steps.login.response.body.token}}"
- id: updateProfile
call: users.updateProfile
with:
headers:
Authorization: "Bearer {{steps.login.response.body.token}}"
body:
name: "{{steps.getProfile.response.body.name}}"
verified: true
# Execute the chain
httpcraft chain userRegistration
# Chain with verbose output
httpcraft chain userRegistration --chain-output full
HttpCraft provides a powerful variable system with multiple scopes and precedence:
- CLI arguments (
--var key=value
) - Step
with
overrides (in chains) - Chain variables
- Endpoint variables
- API variables
- Profile variables
- Global variable files
- Plugin variables
- Secret variables (
{{secret.KEY}}
) - Environment variables (
{{env.KEY}}
) - Dynamic variables (
{{$timestamp}}
)
{{$timestamp}}
- Unix timestamp{{$isoTimestamp}}
- ISO 8601 timestamp{{$randomInt}}
- Random integer{{$guid}}
- UUID v4
# Override variables from CLI
httpcraft myApi getUser --var userId=123 --var format=json
# Use environment variables
export USER_ID=456
httpcraft myApi getUser # Uses {{env.USER_ID}}
# Use secrets (masked in logs)
export API_SECRET=secret123
httpcraft myApi secureEndpoint # Uses {{secret.API_SECRET}}
HttpCraft supports an extensible plugin system with two approaches for plugin definitions:
- Global plugins - Defined once and reused across multiple APIs
- Inline plugins - Defined directly within API configurations for API-specific functionality
Define plugins once in the global plugins
section for reuse across multiple APIs:
# Global plugin definitions
plugins:
- name: "sharedAuth"
path: "./plugins/shared-auth.js"
config:
baseUrl: "https://auth.example.com"
timeout: 30000
- name: "logging"
npmPackage: "httpcraft-logging-plugin"
config:
level: "info"
apis:
userAPI:
baseUrl: "https://api.example.com"
plugins:
# Reference global plugins with API-specific overrides
- name: "sharedAuth"
config:
scope: "user:read user:write"
- name: "logging"
paymentAPI:
baseUrl: "https://payments.example.com"
plugins:
# Same global plugins, different configuration
- name: "sharedAuth"
config:
scope: "payment:read payment:write"
- name: "logging"
config:
level: "debug" # Override global config
Define plugins directly within API configurations for API-specific functionality:
apis:
userAPI:
baseUrl: "https://api.example.com"
plugins:
# Inline plugin with local file
- name: "userValidator"
path: "./plugins/user-validator.js"
config:
strictMode: true
requiredFields: ["email", "username"]
# Inline plugin with npm package
- name: "userMetrics"
npmPackage: "@company/user-metrics-plugin"
config:
trackingId: "user-api-v1"
paymentAPI:
baseUrl: "https://payments.example.com"
plugins:
# Different inline plugins for payment API
- name: "fraudDetection"
path: "./plugins/fraud-detection.js"
config:
threshold: 0.85
- name: "stripeIntegration"
npmPackage: "stripe-httpcraft-plugin"
config:
apiVersion: "2023-10-16"
webhookSecret: "{{secret.STRIPE_WEBHOOK_SECRET}}"
Combine global and inline plugins for maximum flexibility:
# Global plugins for common functionality
plugins:
- name: "oauth2"
config:
clientId: "{{env.OAUTH2_CLIENT_ID}}"
clientSecret: "{{secret.OAUTH2_CLIENT_SECRET}}"
tokenUrl: "https://auth.example.com/oauth2/token"
apis:
userAPI:
baseUrl: "https://api.example.com"
plugins:
# Reference built-in global plugin
- name: "oauth2"
config:
scope: "user:read user:write"
# API-specific inline plugin
- name: "userAudit"
path: "./plugins/user-audit.js"
config:
auditLevel: "detailed"
logDestination: "user-api-logs"
notificationAPI:
baseUrl: "https://notifications.example.com"
plugins:
# Only inline plugins (no global dependencies)
- name: "twilioSMS"
npmPackage: "@httpcraft/twilio-plugin"
config:
accountSid: "{{secret.TWILIO_ACCOUNT_SID}}"
authToken: "{{secret.TWILIO_AUTH_TOKEN}}"
// plugins/customAuth.js
export default {
async setup(context) {
// Pre-request hook
context.registerPreRequestHook(async (request) => {
const token = await getApiToken(context.config.apiKey);
request.headers['Authorization'] = `Bearer ${token}`;
});
// Custom variables
context.registerVariableSource('apiToken', async () => {
return await getApiToken(context.config.apiKey);
});
// Parameterized functions
context.registerParameterizedVariableSource('getTokenWithScope', async (scope) => {
return await getApiToken(context.config.apiKey, scope);
});
// Post-response hook
context.registerPostResponseHook(async (request, response) => {
if (response.headers['content-type']?.includes('xml')) {
response.body = convertXmlToJson(response.body);
}
});
}
};
Global Plugins:
- ✅ Reusability - Define once, use everywhere
- ✅ Consistency - Same plugin version across APIs
- ✅ Maintenance - Single place for updates
- ✅ Configuration sharing - Base config with API overrides
Inline Plugins:
- ✅ Simplicity - No global definition required
- ✅ API-specific - Tailored to specific API needs
- ✅ Reduced ceremony - Direct definition where needed
- ✅ Experimentation - Easy to test one-off plugins
Best Practices:
- Use global plugins for authentication, logging, and shared functionality
- Use inline plugins for API-specific validation, transformations, and integrations
- Combine both approaches in complex applications for optimal flexibility
Enable intelligent tab completion for faster workflows:
# Quick setup - add to your ~/.zshrc
eval "$(httpcraft completion zsh)"
# Or generate completion script manually
httpcraft completion zsh > ~/.zsh/completions/_httpcraft
# Add to .zshrc
echo 'fpath=(~/.zsh/completions $fpath)' >> ~/.zshrc
echo 'autoload -Uz compinit && compinit' >> ~/.zshrc
# Reload shell
source ~/.zshrc
If you encounter _arguments:comparguments:327: can only be called from completion function
, ensure you're using the latest version - this was fixed to properly use compdef
for completion setup.
Note: The completion script automatically initializes the ZSH completion system (compinit
) if needed, so no manual setup is required.
httpcraft <TAB> # Complete API names
httpcraft myApi <TAB> # Complete endpoint names
httpcraft --profile <TAB> # Complete profile names
httpcraft chain <TAB> # Complete chain names
Raw response body sent to stdout
(perfect for piping):
httpcraft api getUsers | jq '.[] | .name'
Detailed request/response information to stderr
:
httpcraft --verbose api getUsers
See what would be sent without making the request:
httpcraft --dry-run api getUsers
Structured JSON output for chains:
httpcraft chain workflow --chain-output full | jq .
0
: Success (including HTTP 4xx/5xx by default)1
: Tool errors, configuration errors, network failures
# Exit with non-zero for HTTP errors
httpcraft --exit-on-http-error 4xx,5xx api getUsers
# Verbose mode shows request/response details
httpcraft --verbose api getUsers
# Dry run shows resolved configuration
httpcraft --dry-run api getUsers
# Check configuration loading
httpcraft --get-api-names
httpcraft --get-endpoint-names myApi
.httpcraft.yaml # Main configuration
apis/ # Modular API definitions
auth.yaml
users.yaml
chains/ # Modular chain definitions
workflows.yaml
plugins/ # Custom plugins
oauth.js
transforms.js
variables/ # Global variables
environments.yaml
secrets.yaml
- Node.js 18+
- npm
git clone <repository>
cd httpcraft
npm install
npm run build
npm test
npm run test:integration
nix develop
For detailed Nix setup and usage instructions, see the Nix Usage Guide.
profiles:
dev:
auth_url: "https://dev-auth.example.com"
api_url: "https://dev-api.example.com"
staging:
auth_url: "https://staging-auth.example.com"
api_url: "https://staging-api.example.com"
prod:
auth_url: "https://auth.example.com"
api_url: "https://api.example.com"
plugins:
- name: "oauth2"
config:
tokenUrl: "{{profile.auth_url}}/oauth2/token"
clientId: "{{env.CLIENT_ID}}"
clientSecret: "{{secret.CLIENT_SECRET}}"
apis:
users:
baseUrl: "{{profile.api_url}}/v1"
plugins:
- name: "oauth2"
chains:
deploymentTest:
description: "End-to-end deployment testing"
vars:
version: "1.2.3"
environment: "staging"
steps:
- id: healthCheck
call: monitoring.health
- id: deploy
call: deployment.deploy
with:
body:
version: "{{version}}"
environment: "{{environment}}"
- id: waitForDeployment
call: deployment.status
with:
pathParams:
deployId: "{{steps.deploy.response.body.id}}"
- id: smokeTest
call: testing.smokeTest
with:
body:
deploymentId: "{{steps.deploy.response.body.id}}"
tests: ["api", "database", "cache"]
- id: notifySuccess
call: notifications.slack
with:
body:
message: "Deployment {{version}} to {{environment}} completed successfully"
results: "{{steps.smokeTest.response.body}}"
- Fork the repository
- Create a feature branch
- Make your changes
- Add tests
- Submit a pull request
[License details here]
HttpCraft - Making HTTP testing simple, powerful, and enjoyable! 🚀
HttpCraft includes comprehensive tests built with Vitest. Run the test suite with:
npm test
HttpCraft supports comprehensive test coverage reporting. You can generate coverage reports in multiple formats:
# Run tests with coverage (generates text, JSON, and HTML reports)
npm run test:coverage
# Run tests with coverage in watch mode
npm run test:coverage:watch
# Run tests with coverage and open the UI interface
npm run test:coverage:ui
Coverage reports are generated in the coverage/
directory:
- Text report: Displayed in terminal during test run
- HTML report: Open
coverage/index.html
in your browser for interactive coverage exploration - JSON report:
coverage/coverage-final.json
for programmatic access
The project maintains coverage thresholds of 80% for:
- Branches: Decision points in code
- Functions: Function coverage
- Lines: Line coverage
- Statements: Statement coverage
Coverage excludes test files, configuration files, examples, and documentation to focus on source code quality.