@signwell/cli
v1.0.0
Published
A top-tier Node.js CLI for the SignWell eSignature API
Readme
signwell-cli (sw)
A full-featured Node.js CLI for the SignWell eSignature API. Manage documents, templates, bulk sends, webhooks, and more from the terminal.
Table of Contents
- Installation
- Quick Start
- Use Cases
- Global Options
- Configuration
- AI Agent Skills
- Commands
- auth - Manage authentication
- me - Show account info
- profile - Manage profiles
- documents - Create, send, track, and download documents
- templates - Manage reusable document templates
- bulk-send - Send documents in bulk via CSV
- webhooks - Manage and test webhook integrations
- schema - Print JSON Schemas for LLM/CI integration
- skills - Install AI agent skills
- Output Modes
- JSON Envelope
- Data Shapes
- API Endpoints Reference
- Error Codes & Exit Codes
- Environment Variables
- File Upload
- Pagination
- Dependency Pinning & Security
- Development
Installation
# Install globally
npm install -g @signwell/cli
# Or run directly without installing
npx @signwell/cli --help
npx @signwell/cli documents list
# Or from source
git clone https://github.com/Bidsketch/signwell-cli.git
cd signwell-cli
npm ci
npm run build
npm linkRequires Node.js >= 18.
Quick Start
# Authenticate
sw auth login --api-key YOUR_API_KEY
# Create a draft from an uploaded document
sw documents create \
--file contract.pdf \
--recipient "[email protected]:Alice Smith" \
--name "Service Agreement"
# List documents
sw documents list
# Download a completed document
sw documents download doc_abc123 -o signed.pdf
# Create a draft from a template
sw templates use tmpl_xyz \
--recipient "Signer:[email protected]:Bob" \
--field "company=Acme Inc"Use Cases
Freelancer: Send a contract and get it signed
# Create a contract draft and add fields in SignWell
sw documents create \
--file proposal.pdf \
--recipient "[email protected]:Jane Lee" \
--name "Web Design Proposal"
# Check if it's been signed yet
sw documents list --status pending
# Nudge the client after a few days
sw documents remind doc_abc123
# Download the signed copy once complete
sw documents download doc_abc123 -o proposal-signed.pdfHR: Onboard new hires with an offer letter template
# Set up a reusable offer letter
sw templates create \
--file offer-letter.pdf \
--name "Offer Letter" \
--placeholder "New Hire:[email protected]:New Hire" \
--text-tags
# Create a draft for a new hire, pre-filling the start date
sw templates use tmpl_offer \
--recipient "New Hire:[email protected]:Maria Chen" \
--field "start_date=2026-04-01" \
--field "position=Software Engineer"Sales: Batch-send NDAs to a list of prospects
# Download the CSV template for your NDA template
sw bulk-send csv-template --template tmpl_nda -o nda-batch.csv
# Fill in the CSV with prospect info, then validate it
sw bulk-send validate --template tmpl_nda --csv nda-batch.csv
# Send the batch
sw bulk-send create \
--template tmpl_nda \
--csv nda-batch.csv \
--name "Q1 Prospect NDAs"
# Monitor progress
sw bulk-send get bs_abc123Developer: Pipe document data into other tools
# Get all completed document IDs as plain text
sw documents list --status completed --all --json \
| jq -r '.id'
# Count pending documents
sw documents list --status pending --json \
| jq '.meta.total'
# Export all documents to a CSV with jq
sw documents list --all --json \
| jq -r '[.id, .name, .status] | @csv'Testing: Try things out without sending real emails
# Log in with test mode enabled
sw auth login --api-key sk_test_xyz --test-mode
# Or toggle test mode per-command
sw documents create \
--file draft.pdf \
--recipient "[email protected]:Test User" \
--name "Test Doc" \
--test-mode
# Everything works the same, but no emails are delivered
sw documents listWebhook development: Inspect events locally
# Start a local listener to see webhook payloads as they arrive
sw webhooks listen --port 4000
# In another terminal, register the webhook (use ngrok or similar to expose)
sw webhooks create --url https://abc123.ngrok.io/hooks \
--event document_completed --event document_signed
# Send a test document and watch events print in real timeMulti-account: Switch between production and staging
# Set up both profiles
sw profile add production --api-key sk_live_abc123
sw profile add staging --api-key sk_test_xyz789 --test-mode
# Default to production
sw profile use production
# Run a one-off command against staging
sw documents list --profile staging
# See all profiles at a glance
sw profile listGlobal Options
Every command accepts these flags:
| Flag | Type | Default | Description |
|------|------|---------|-------------|
| --profile <name> | string | active profile | Use a named profile from config |
| --json | boolean | false | Output machine-readable JSON envelope |
| --quiet | boolean | false | Suppress all output except errors |
| --no-color | boolean | false | Disable ANSI color codes. Bare --no-color and --no-color=1 both disable color; omit it to keep color enabled. |
| --test-mode | boolean | false | Inject test_mode: true into POST/PUT/PATCH request bodies |
| --debug | boolean | false | Log HTTP requests/responses to stderr |
| --help, -h | | | Show help for any command |
| --version | | | Show CLI version |
Configuration
Config File
Stored at ~/.signwell/config.json:
{
"profiles": {
"default": {
"api_key": "your_api_key_here",
"test_mode": false
},
"staging": {
"api_key": "staging_key_here",
"test_mode": true
}
},
"active_profile": "default"
}Environment Variables
Stored CLI profiles are the credential source for normal account-scoped commands. SIGNWELL_API_KEY is ignored by normal commands, including after sw auth logout; configure credentials with sw auth login or sw profile add.
| Variable | Description |
|----------|-------------|
| SIGNWELL_API_KEY | Ignored for normal commands; use sw auth login --api-key to store credentials in a profile |
| SIGNWELL_TEST_MODE | Set to "true" to enable test mode |
| SIGNWELL_API_BASE_URL | Custom API base URL (default: https://www.signwell.com/api/v1) |
| SIGNWELL_CONFIG_PATH | Custom config file location |
| SIGNWELL_AUTO_CONFIRM | Set to "true" to skip all confirmation prompts |
Credential Priority
- Explicit
--api-keyflag for auth setup commands. - Config file profile selected by
--profile <name>or the active profile. - No implicit environment-variable credential fallback.
AI Agent Skills
signwell-cli ships with Agent Skills — portable skill files that teach AI coding assistants how to use the sw CLI effectively. Skills work with Claude Code, Cursor, GitHub Copilot, Gemini CLI, Windsurf, Codex, Roo Code, and any other agent that supports the open Agent Skills standard.
Installation
Option 1: Universal installer (recommended)
Use the skills CLI to install from GitHub. This auto-detects all supported agents on your machine:
npx skills add ziptied/signwell-cliOption 2: Built-in command
If you already have signwell-cli installed globally, the skill installer is built right in:
sw skills installThis will:
- Copy the bundled skills to the canonical
~/.agents/skills/directory - Auto-detect installed AI agents (Claude Code, Cursor, Copilot, etc.)
- Create symlinks from each agent's skill directory into the canonical location when possible
- Keep existing installs unchanged unless
--forceis passed
You can also target a specific agent:
sw skills install --agent claude-code
sw skills install --agent cursorOption 3: Manual
Copy the skills/signwell-cli/ directory from this repo into your agent's skills directory:
# Claude Code
cp -r skills/signwell-cli ~/.claude/skills/
# Cursor
cp -r skills/signwell-cli ~/.cursor/skills/
# Any agent using the universal directory
cp -r skills/signwell-cli ~/.agents/skills/What the skill provides
Once installed, your AI agent will know how to:
- Authenticate with
sw auth login - Create, send, track, and download documents
- Manage templates and bulk sends
- Configure webhooks
- Use
--jsonmode for structured output parsing - Handle pagination, error codes, and common workflows
Repo structure
Skills follow the Agent Skills specification:
skills/
signwell-cli/
SKILL.md # Skill instructions + metadataSupported agents
| Agent | Global skills directory |
|-------|----------------------|
| Claude Code | ~/.claude/skills/ |
| Cursor | ~/.cursor/skills/ |
| GitHub Copilot | ~/.copilot/skills/ |
| Gemini CLI | ~/.gemini/skills/ |
| Codex | ~/.codex/skills/ |
| Windsurf | ~/.codeium/windsurf/skills/ |
| Roo Code | ~/.roo/skills/ |
| Universal | ~/.agents/skills/ |
Commands
auth
Manage authentication credentials.
sw auth login
Set up API credentials interactively or via flags.
# Interactive (prompts for key)
sw auth login
# Non-interactive
sw auth login --api-key sk_live_abc123
# Save to a named profile
sw auth login --api-key sk_live_abc123 --profile production
# Enable test mode for this profile
sw auth login --api-key sk_test_xyz --test-modeOptions:
| Option | Type | Description |
|--------|------|-------------|
| --api-key | string | API key (skips interactive prompt) |
| --test-mode | boolean | Enable test mode for this profile |
JSON output:
{
"success": true,
"error": null,
"data": {
"name": "John Doe",
"email": "[email protected]",
"profile": "default"
},
"meta": {}
}Behavior: Validates the API key by calling GET /me before saving. If validation fails, the key is not saved.
sw auth logout
Remove stored credentials.
sw auth logout
sw auth logout --profile stagingAfter the selected profile is removed, normal API commands are unauthenticated until another profile is configured. SIGNWELL_API_KEY does not act as a fallback credential.
Options:
| Option | Type | Description |
|--------|------|-------------|
| --profile | string | Profile to remove (default: active profile) |
JSON output:
{
"success": true,
"data": { "removed": "default" },
"error": null,
"meta": {}
}sw auth status
Show current authentication status.
sw auth status
sw auth status --profile productionJSON output:
{
"success": true,
"data": {
"authenticated": true,
"profile": "default",
"credential_source": "profile",
"api_key": "sk_l...c123",
"test_mode": false,
"env_api_key_set": true,
"env_api_key_conflict": false,
"env_api_key_ignored": false
},
"error": null,
"meta": {}
}me
sw me
Show current account information. Calls GET /me.
sw me
sw me --jsonJSON output:
{
"success": true,
"data": {
"id": "user_abc123",
"role": "admin",
"user": {
"id": "user_abc123",
"name": "John Doe",
"email": "[email protected]"
},
"account": {
"id": "acct_xyz",
"name": "Acme Corp",
"plan_tier": "Business"
}
},
"error": null,
"meta": {}
}profile
Manage multiple configuration profiles.
sw profile list
sw profile listHuman output:
Name API Key Test Mode
→ default sk_l...c123 no
staging sk_t...xyz yesJSON output:
{
"success": true,
"data": [
{ "name": "default", "active": true, "api_key": "sk_l...c123", "test_mode": false },
{ "name": "staging", "active": false, "api_key": "sk_t...xyz", "test_mode": true }
],
"error": null,
"meta": {}
}sw profile add <name>
sw profile add production --api-key sk_live_abc123
sw profile add staging --api-key sk_test_xyz --test-mode| Option | Type | Description |
|--------|------|-------------|
| --api-key | string | API key (prompts if omitted) |
| --test-mode | boolean | Enable test mode (default: false) |
JSON output: { "data": { "name": "production", "added": true } }
sw profile use <name>
sw profile use productionJSON output: { "data": { "active_profile": "production" } }
sw profile remove <name>
sw profile remove staging
sw profile remove staging --confirm # skip prompt| Option | Type | Description |
|--------|------|-------------|
| --confirm | boolean | Skip confirmation prompt |
JSON output: { "data": { "name": "staging", "removed": true } }
sw profile show <name>
sw profile show productionJSON output:
{
"data": {
"name": "production",
"active": false,
"api_key": "sk_l...c123",
"test_mode": false
}
}documents
Create, send, track, and download signature documents.
sw documents create
Create a new document for signing.
# Simple document with local file
sw documents create \
--file contract.pdf \
--recipient "[email protected]:Alice Smith" \
--name "Service Agreement"
# Multiple files and recipients as a draft
sw documents create \
--file contract.pdf \
--file appendix.pdf \
--recipient "[email protected]:Alice Smith" \
--recipient "[email protected]:Bob Jones" \
--subject "Please sign" \
--message "Attached for your signature."
# Send immediately when the file contains SignWell text tags
sw documents create \
--file contract.pdf \
--recipient "[email protected]:Alice Smith" \
--name "Tagged Service Agreement" \
--text-tags \
--send
# File from URL
sw documents create \
--file-url "https://example.com/contract.pdf" \
--recipient "[email protected]:Alice"
# Embedded signing
sw documents create \
--file contract.pdf \
--recipient "[email protected]:Alice:embedded" \
--name "Embedded Doc"
# Draft with options
sw documents create \
--file contract.pdf \
--recipient "[email protected]:Alice" \
--text-tags \
--signing-order \
--expiration-days 30 \
--reminder-days 3 7 14Options:
| Option | Type | Required | Description |
|--------|------|----------|-------------|
| --file <path> | string[] | one of file/file-url/file-b64 | Local file path(s) |
| --file-url <url> | string[] | | Remote file URL(s) |
| --file-b64 <path> | string | | Path to base64-encoded file |
| --file-b64-name <name> | string | | Filename for base64 upload |
| --name <name> | string | | Document name (default: first filename) |
| --recipient <spec> | string[] | yes | "email:name" or "email:name:embedded" |
| --subject | string | | Email subject line |
| --message | string | | Email message body |
| --draft | boolean | | Create as draft (default behavior) |
| --send | boolean | | Send after creation; requires --text-tags for file uploads |
| --text-tags | boolean | | Enable text tag parsing before sending |
| --redirect-url | string | | Redirect URL after signing |
| --signing-order | boolean | | Enforce sequential signing order |
| --expiration-days | number | | Days until document expires |
| --reminder-days | number[] | | Auto-remind at these day intervals |
Recipient format: email:name:embedded
email(required): Signer's email addressname(optional): Display nameembedded(optional): Use embedded signing (returnsembedded_signing_url)
API: POST /documents
JSON output (default - draft):
{
"success": true,
"data": {
"id": "doc_abc123",
"name": "Service Agreement",
"status": "draft",
"created_at": "2024-01-15T10:30:00Z",
"recipients": [
{
"id": "rec_xyz",
"email": "[email protected]",
"name": "Alice Smith",
"status": "pending"
}
]
},
"error": null,
"meta": {}
}Note: Documents are always created as drafts unless
--sendis also passed. Pass--text-tags --sendto send immediately; the returned document will havestatus: pendingand each recipient will include asigning_url.
sw documents get <id>
Get full document details.
sw documents get doc_abc123
sw documents get doc_abc123 --jsonAPI: GET /documents/{id}
JSON output: Full Document object (see Data Shapes).
Human output: Document summary with a recipients table showing email, name, status, and signed-at date.
sw documents list
List documents with pagination.
sw documents list
sw documents list --page 2 --per-page 50
sw documents list --limit 30 --page 2
sw documents list --status completed
sw documents list --name small-contract --status completed
sw documents list --query "name:Classic AND status:completed AND start_date:2026-01-31"
sw documents list --all # fetch all pages
sw documents list --all --json # NDJSON stream of all documents| Option | Type | Default | Description |
|--------|------|---------|-------------|
| --page | number | 1 | Page number |
| --per-page / --limit | number | 20 | Items per page, max 50 |
| --query | string | | Raw API filter query, e.g. name:Classic AND status:completed |
| --name | string | | Filter by document name |
| --status | string | | Filter by document status. Common API statuses include draft, saved, sent, shared, viewed, pending, completed, expired, canceled, declined, bounced, and error. |
| --person | string | | Filter by signer/person |
| --start-date | string | | Filter documents created on or after YYYY-MM-DD |
| --end-date | string | | Filter documents created on or before YYYY-MM-DD |
| --document-ids | string[] | | Filter by document ID(s), comma-separated or repeated |
| --all / --all-pages | boolean | false | Fetch all pages |
API: GET /documents?page={page}&limit={limit}&query={query}
All document list filters are serialized into query= and joined with AND. Supported filter keys are name, status, person, start_date, end_date, and document_ids. Dates must use YYYY-MM-DD, and OR filters are not supported. keyword is not exposed because it is not a confirmed document list filter key.
JSON output (single page):
{
"success": true,
"data": [
{ "id": "doc_1", "name": "Contract A", "status": "completed", "created_at": "..." },
{ "id": "doc_2", "name": "NDA", "status": "pending", "created_at": "..." }
],
"error": null,
"meta": {
"count": 2,
"total": 45,
"page": 1,
"per_page": 20,
"total_pages": 3,
"next_page": 2,
"prev_page": null
}
}JSON output (--all): Newline-delimited JSON (NDJSON), one document per line.
sw documents send <id>
Send a draft document for signing.
sw documents send doc_abc123API: POST /documents/{id}/send
JSON output: Updated Document object.
sw documents remind <id>
Send a reminder to all pending signers.
sw documents remind doc_abc123API: POST /documents/{id}/remind
JSON output:
{ "success": true, "data": { "id": "doc_abc123", "reminded": true }, "error": null, "meta": {} }sw documents download <id>
Download the completed, signed PDF.
sw documents download doc_abc123
sw documents download doc_abc123 -o ~/Downloads/signed-contract.pdf
sw documents download doc_abc123 --open| Option | Type | Default | Description |
|--------|------|---------|-------------|
| --output, -o | string | {name}-signed.pdf | Output file path |
| --open | boolean | false | Open file after download |
API: GET /documents/{id}/completed_pdf
Prerequisite: Document status must be completed. Exits with error if not.
JSON output:
{ "success": true, "data": { "id": "doc_abc123", "output": "contract-signed.pdf", "size": 102400 }, "error": null, "meta": {} }sw documents delete <id>
Delete a document permanently.
sw documents delete doc_abc123
sw documents delete doc_abc123 --confirm # skip prompt| Option | Type | Description |
|--------|------|-------------|
| --confirm | boolean | Skip confirmation prompt |
API: DELETE /documents/{id}
JSON output:
{ "success": true, "data": { "id": "doc_abc123", "deleted": true }, "error": null, "meta": {} }sw documents recipients update <id>
Update recipients on an existing document.
sw documents recipients update doc_abc123 \
--recipient "[email protected]:[email protected]:New Name"| Option | Type | Required | Description |
|--------|------|----------|-------------|
| --recipient | string[] | yes | "old_email:new_email:new_name" |
API: PATCH /documents/{id}/recipients
JSON output: Updated Document object.
templates
Manage reusable document templates.
sw templates create
sw templates create \
--file nda.pdf \
--name "Standard NDA" \
--placeholder "Client:[email protected]:Client Rep" \
--text-tags| Option | Type | Required | Description |
|--------|------|----------|-------------|
| --file <path> | string[] | one of file/file-url/file-b64 | Local file path(s) |
| --file-url <url> | string[] | | Remote file URL(s) |
| --file-b64 <path> | string | | Path to base64-encoded file |
| --file-b64-name <name> | string | | Filename for base64 upload |
| --name | string | yes | Template name |
| --placeholder | string[] | | "role_name:email:display_name" |
| --text-tags | boolean | | Enable text tag parsing |
| --fields | string | | Path to fields JSON file |
A template is only usable by sw templates use once it has fields. Either pass a file that contains SignWell text tags with --text-tags, supply explicit field coordinates via --fields fields.json, or open the template in SignWell and add fields manually.
API: POST /document_templates
JSON output: Template object.
sw templates get <id>
sw templates get tmpl_abc123API: GET /document_templates/{id}
JSON output: Full Template object with placeholder_roles and fields.
sw templates list
sw templates list
sw templates list --page 2 --per-page 50
sw templates list --limit 30 --page 2
sw templates list --status Available
sw templates list --name nda --status Available
sw templates list --query "name:Classic AND status:Available AND start_date:2026-01-31"
sw templates list --all
sw templates list --all --json # NDJSON stream of all templates| Option | Type | Default | Description |
|--------|------|---------|-------------|
| --page | number | 1 | Page number |
| --per-page / --limit | number | 20 | Items per page |
| --query | string | | Raw API filter query, e.g. name:Classic AND status:Available |
| --name | string | | Filter by template name |
| --status | string | | Filter by template status |
| --start-date | string | | Filter templates created on or after YYYY-MM-DD |
| --end-date | string | | Filter templates created on or before YYYY-MM-DD |
| --template-ids | string[] | | Filter by template ID(s), comma-separated or repeated |
| --all / --all-pages | boolean | false | Fetch all pages |
API: GET /document_templates?page={page}&per_page={per_page}&query={query}
Template list filters are serialized into query= and joined with AND. Supported CLI filter keys are name, status, start_date, end_date, and template_ids. Dates must use YYYY-MM-DD, and OR filters are not supported.
JSON output: Paginated array of Template objects with meta.
sw templates update <id>
sw templates update tmpl_abc123 --name "Updated NDA"
sw templates update tmpl_abc123 --file new-nda.pdf| Option | Type | Description |
|--------|------|-------------|
| --name | string | New template name |
| --file | string[] | Replacement file(s) |
API: PUT /document_templates/{id}
JSON output: Updated Template object.
sw templates delete <id>
sw templates delete tmpl_abc123 --confirm| Option | Type | Description |
|--------|------|-------------|
| --confirm | boolean | Skip confirmation |
API: DELETE /document_templates/{id}
JSON output: { "data": { "id": "tmpl_abc123", "deleted": true } }
sw templates use <id>
Create a draft document from a template. Use sw documents send <id> after reviewing the draft.
sw templates use tmpl_abc123 \
--recipient "Signer:[email protected]:Alice Smith" \
--field "company=Acme Inc" \
--field "date=2024-01-15"
# Simple recipient (no placeholder role)
sw templates use tmpl_abc123 \
--recipient "[email protected]:Bob"| Option | Type | Required | Description |
|--------|------|----------|-------------|
| --recipient | string[] | yes | "placeholder:email:name" or "email:name" |
| --field | string[] | | Pre-fill field as "key=value" |
| --subject | string | | Override email subject |
| --message | string | | Override email message |
| --send | boolean | | Create as draft, then send explicitly |
| --draft | boolean | | Create as draft (default behavior) |
Template use defaults to drafts so the document can be reviewed before sending. The template must already contain the required fields before a send will succeed.
Recipient formats:
Signer:[email protected]:Alice Smith— maps to a template placeholder role[email protected]:Alice Smith— simple recipient (no placeholder)
API: POST /document_templates/documents
JSON output: Created Document object.
bulk-send
Create and manage bulk document sends from CSV files.
sw bulk-send create
sw bulk-send create \
--template tmpl_abc123 \
--csv recipients.csv \
--name "Q1 NDA Batch"
# Dry run (validate only)
sw bulk-send create \
--template tmpl_abc123 \
--csv recipients.csv \
--dry-run
# Limit rows
sw bulk-send create \
--template tmpl_abc123 \
--csv recipients.csv \
--limit 10| Option | Type | Required | Description |
|--------|------|----------|-------------|
| --template | string[] | yes | Template ID(s) |
| --csv | string | yes | Path to CSV file |
| --name | string | | Bulk send name |
| --dry-run | boolean | | Validate CSV without creating |
| --limit | number | | Process only first N rows |
| --confirm | boolean | | Skip confirmation prompt |
API: POST /bulk_sends
JSON output:
{
"success": true,
"data": {
"id": "bs_abc123",
"name": "Q1 NDA Batch",
"status": "processing",
"total": 50,
"sent": 0,
"failed": 0,
"created_at": "2024-01-15T10:30:00Z"
},
"error": null,
"meta": {}
}sw bulk-send get <id>
sw bulk-send get bs_abc123API: GET /bulk_sends/{id}
JSON output: BulkSend object.
sw bulk-send list
sw bulk-send list
sw bulk-send list --all| Option | Type | Default | Description |
|--------|------|---------|-------------|
| --page | number | 1 | Page number |
| --per-page | number | 20 | Items per page |
| --all / --all-pages | boolean | false | Fetch all pages |
API: GET /bulk_sends?page={page}&per_page={per_page}
sw bulk-send documents <id>
List documents created by a bulk send.
sw bulk-send documents bs_abc123
sw bulk-send documents bs_abc123 --all| Option | Type | Default | Description |
|--------|------|---------|-------------|
| --page | number | 1 | Page number |
| --per-page | number | 20 | Items per page |
| --all / --all-pages | boolean | false | Fetch all pages |
API: GET /bulk_sends/{id}/documents?page={page}&per_page={per_page}
sw bulk-send csv-template
Download a blank CSV template with the correct headers for a template.
sw bulk-send csv-template --template tmpl_abc123
sw bulk-send csv-template --template tmpl_abc123 -o my-template.csv| Option | Type | Default | Description |
|--------|------|---------|-------------|
| --template | string[] | required | Template ID(s) |
| --output, -o | string | bulk_template.csv | Output file path |
API: GET /bulk_sends/csv_template?template_ids[]={id}
JSON output:
{ "success": true, "data": { "output": "bulk_template.csv" }, "error": null, "meta": {} }sw bulk-send validate
Validate a CSV file against a template without creating a bulk send.
sw bulk-send validate --template tmpl_abc123 --csv recipients.csv| Option | Type | Required | Description |
|--------|------|----------|-------------|
| --template | string[] | yes | Template ID(s) |
| --csv | string | yes | Path to CSV file |
API: POST /bulk_sends/validate_csv
JSON output: Validation result with per-row errors if any.
webhooks
Manage and test webhook integrations.
sw webhooks list
sw webhooks listAPI: GET /hooks
JSON output:
{
"success": true,
"data": [
{
"id": "hook_abc123",
"callback_url": "https://myapp.com/webhooks/signwell",
"event_types": ["document_completed", "document_signed"],
"created_at": "2024-01-25T10:00:00Z"
}
],
"error": null,
"meta": {}
}sw webhooks create
sw webhooks create --url https://myapp.com/hooks
sw webhooks create --url https://myapp.com/hooks --event document_completed --event document_signed| Option | Type | Required | Description |
|--------|------|----------|-------------|
| --url | string | yes | Webhook endpoint URL |
| --event | string[] | | Event type(s) to listen for |
API: POST /hooks
JSON output:
{
"success": true,
"data": {
"id": "hook_abc123",
"callback_url": "https://myapp.com/hooks"
},
"error": null,
"meta": {}
}sw webhooks delete <id>
sw webhooks delete hook_abc123 --confirm| Option | Type | Description |
|--------|------|-------------|
| --confirm | boolean | Skip confirmation |
API: DELETE /hooks/{id}
JSON output: { "data": { "id": "hook_abc123", "deleted": true } }
sw webhooks listen
Start a local HTTP server to receive and inspect webhook events during development.
sw webhooks listen
sw webhooks listen --port 4000
sw webhooks listen --secret whsec_abc123
sw webhooks listen --forward http://localhost:8080/hooks| Option | Type | Default | Description |
|--------|------|---------|-------------|
| --port | number | 3000 | Port to listen on |
| --secret | string | | Local listener HMAC secret to validate incoming signatures |
| --forward | string | | Forward received events to this URL |
Features:
- Accepts
POSTrequests on any path - Validates HMAC-SHA256 signature via
x-signwell-signatureheader when you provide--secret. The create-webhook API does not return or manage a secret field. - Pretty-prints each webhook event to the console with timestamp
- Optionally forwards events to another service (preserves signature header)
- Returns
200 OKfor valid events,401 Unauthorizedfor invalid signatures,405for non-POST
schema
Print JSON Schemas for commands. Useful for LLM integrations, code generation, and CI validation.
sw schema <command>
sw schema documents.create
sw schema templates.use
sw schema bulk-send.createAvailable schemas:
| Schema Key | Description |
|------------|-------------|
| documents.create | Create a document for signing |
| documents.get | Get document details |
| documents.list | List documents |
| documents.send | Send a draft document |
| documents.delete | Delete a document |
| templates.create | Create a template |
| templates.list | List templates |
| templates.use | Create document from template |
| bulk-send.create | Create a bulk send |
| webhooks.create | Create a webhook |
Output format: Each response contains command, description, input_schema, and output_schema as JSON Schema objects derived from the live Zod definitions in src/commands/schema.ts. Run the command directly for the canonical, always-current output:
sw schema documents.create
sw schema templates.useNote:
sw schemaoutputs raw JSON; it is not wrapped in the standard--jsonenvelope.
skills
Install AI agent skills bundled with signwell-cli.
sw skills install
sw skills install # auto-detect agents
sw skills install --agent claude-code # specific agent
sw skills install --force # overwrite existing
sw skills install --json # machine-readable output| Option | Type | Default | Description |
|--------|------|---------|-------------|
| --agent | string | auto-detect | Install for a specific agent only |
| --force | boolean | false | Overwrite existing skill installations |
Available agents: claude-code, cursor, windsurf, codex, github-copilot, gemini-cli, roo
Behavior:
- Copies skills to canonical
~/.agents/skills/directory - Detects installed AI agents on the system
- Creates symlinks from each agent's skills directory to the canonical location when possible
- Falls back to copying if symlinks are not supported (e.g., Windows without developer mode)
- Keeps existing installs unchanged unless
--forceis passed
JSON output:
{
"success": true,
"data": {
"skills": ["signwell-cli"],
"canonical_path": "/home/user/.agents/skills",
"agents": [
{ "agent": "Claude Code", "skill": "signwell-cli", "method": "symlink" },
{ "agent": "Cursor", "skill": "signwell-cli", "method": "existing" }
]
},
"error": null,
"meta": {}
}Output Modes
Human Mode (default)
Rich terminal output with colors, spinners, and terminal tables.
sw documents listJSON Mode (--json)
Machine-readable JSON envelope on stdout. Errors go to stderr.
sw documents list --json
sw documents list --json | jq '.data[].id'Quiet Mode (--quiet)
Suppress all output except errors. Useful for scripting.
sw documents send doc_abc123 --quietNDJSON Streaming (--all --json)
When using --all with --json, documents are streamed as newline-delimited JSON (one object per line):
sw documents list --all --json | while IFS= read -r line; do
echo "$line" | jq '.id'
doneJSON Envelope
All --json output follows this envelope format, with two exceptions:
sw schema <command>outputs raw JSON (not envelope-wrapped).sw <command> --all --jsonstreams NDJSON (one raw data object per line, no envelope).
Success
{
"success": true,
"error": null,
"data": { ... },
"meta": {
"count": 20,
"total": 145,
"page": 1,
"per_page": 20,
"total_pages": 8,
"next_page": 2,
"prev_page": null
}
}Error
{
"success": false,
"error": {
"code": "NOT_FOUND",
"message": "Request failed with status code 404",
"hint": "Verify the ID is correct",
"http_status": 404
},
"data": null,
"meta": {}
}Data Shapes
Document
{
id: string;
name: string;
status: string; // e.g. draft, saved, sent, shared, viewed, pending, completed, expired, canceled, declined, bounced, error
created_at: string; // ISO 8601
updated_at?: string;
expires_at?: string;
subject?: string;
message?: string;
test_mode?: boolean;
recipients: Recipient[];
files?: DocumentFile[];
}Recipient
{
id: string;
email: string;
name: string;
status: string; // e.g. pending, signed, declined
signing_url?: string; // URL for the signer
embedded_signing_url?: string; // URL for iframe embedding
embedded_signing?: boolean;
signed_at?: string; // ISO 8601
last_viewed_at?: string;
}Template
{
id: string;
name: string;
created_at: string;
updated_at?: string;
placeholder_roles?: PlaceholderRole[];
fields?: TemplateField[];
}PlaceholderRole
{
name: string; // e.g. "Signer", "Witness"
email?: string;
}TemplateField
{
name: string;
type: string; // e.g. "text", "date", "checkbox"
required?: boolean;
}BulkSend
{
id: string;
name?: string;
status: string; // e.g. processing, completed, failed
total: number;
sent?: number;
failed?: number;
created_at: string;
}Webhook
{
id: string;
callback_url: string;
event_types?: string[];
created_at?: string;
}DocumentFile
{
name: string;
file_base64?: string; // Base64-encoded file content
file_url?: string; // Remote URL
}PaginatedResponse
{
data: T[];
total: number;
page: number;
per_page: number;
total_pages: number;
}API Endpoints Reference
All endpoints are relative to the base URL (default: https://www.signwell.com/api/v1).
Authentication: X-Api-Key header.
| Method | Endpoint | Command | Description |
|--------|----------|---------|-------------|
| GET | /me | sw me | Get account info |
| POST | /documents | sw documents create | Create document |
| GET | /documents/{id} | sw documents get | Get document |
| GET | /documents | sw documents list | List documents |
| POST | /documents/{id}/send | sw documents send | Send draft |
| POST | /documents/{id}/remind | sw documents remind | Send reminder |
| GET | /documents/{id}/completed_pdf | sw documents download | Download signed PDF |
| DELETE | /documents/{id} | sw documents delete | Delete document |
| PATCH | /documents/{id}/recipients | sw documents recipients update | Update recipients |
| POST | /document_templates/documents | sw templates use | Create from template |
| POST | /document_templates | sw templates create | Create template |
| GET | /document_templates/{id} | sw templates get | Get template |
| GET | /document_templates | sw templates list | List templates |
| PUT | /document_templates/{id} | sw templates update | Update template |
| DELETE | /document_templates/{id} | sw templates delete | Delete template |
| POST | /bulk_sends | sw bulk-send create | Create bulk send |
| GET | /bulk_sends/{id} | sw bulk-send get | Get bulk send |
| GET | /bulk_sends | sw bulk-send list | List bulk sends |
| GET | /bulk_sends/{id}/documents | sw bulk-send documents | List bulk send docs |
| GET | /bulk_sends/csv_template | sw bulk-send csv-template | Download CSV template |
| POST | /bulk_sends/validate_csv | sw bulk-send validate | Validate CSV |
| GET | /hooks | sw webhooks list | List webhooks |
| POST | /hooks | sw webhooks create | Create webhook |
| DELETE | /hooks/{id} | sw webhooks delete | Delete webhook |
Error Codes & Exit Codes
HTTP Status to Error Code Mapping
| HTTP Status | Error Code | Default Hint |
|-------------|------------|--------------|
| 401 | UNAUTHORIZED | Run sw auth login to update your credentials |
| 403 | FORBIDDEN | Your API key does not have permission for this action |
| 404 | NOT_FOUND | Verify the ID is correct |
| 422 | VALIDATION_ERROR | Check required fields with sw schema <command> |
| 429 | RATE_LIMITED | Retry in a few seconds or reduce request frequency |
| 500 | SERVER_ERROR | This is a SignWell server error. Try again later |
| 503 | SERVICE_UNAVAILABLE | SignWell API is temporarily unavailable. Retrying automatically... |
| network | NETWORK_ERROR | Check SIGNWELL_API_BASE_URL and your network connection |
| other | UNKNOWN_ERROR | An unexpected error occurred |
Exit Codes
| Code | Meaning | When |
|------|---------|------|
| 0 | Success | Command completed successfully |
| 1 | General error | Unclassified CLI or API error |
| 2 | Usage error | Invalid arguments, missing required options |
| 3 | Auth error | Missing/invalid API key, 401 Unauthorized |
| 4 | Rate limited | 429 Too Many Requests |
| 5 | File error | File not found, unsupported type, read failure |
| 6 | CSV error | CSV parse failure, empty CSV, validation error |
Error Classes
| Class | Exit Code | Description |
|-------|-----------|-------------|
| CliError | 1 | Base error class |
| UsageError | 2 | Invalid CLI arguments |
| AuthError | 3 | Authentication issues |
| FileError | 5 | File system errors |
| CsvError | 6 | CSV parsing/validation errors |
Automatic Retry
The client automatically retries (with exponential backoff) on:
429 Too Many Requests503 Service Unavailable- Network errors (no response)
Default: 3 retries. Rate limit warnings are displayed when fewer than 5 requests remain.
Environment Variables
| Variable | Description | Default |
|----------|-------------|---------|
| SIGNWELL_API_KEY | Ignored for normal commands; store credentials with sw auth login | — |
| SIGNWELL_TEST_MODE | "true" to enable test mode | false |
| SIGNWELL_API_BASE_URL | Custom API endpoint | https://www.signwell.com/api/v1 |
| SIGNWELL_CONFIG_PATH | Custom config file path | ~/.signwell/config.json |
| SIGNWELL_AUTO_CONFIRM | "true" to skip all prompts | false |
| NO_COLOR | Disable colors (standard) | — |
File Upload
Supported File Types
| Extension | MIME Type |
|-----------|----------|
| .pdf | application/pdf |
| .doc | application/msword |
| .docx | application/vnd.openxmlformats-officedocument.wordprocessingml.document |
| .pages | application/vnd.apple.pages |
| .ppt | application/vnd.ms-powerpoint |
| .pptx | application/vnd.openxmlformats-officedocument.presentationml.presentation |
| .key | application/vnd.apple.keynote |
| .xls | application/vnd.ms-excel |
| .xlsx | application/vnd.openxmlformats-officedocument.spreadsheetml.sheet |
| .numbers | application/vnd.apple.numbers |
| .jpg / .jpeg | image/jpeg |
| .png | image/png |
| .tiff / .tif | image/tiff |
| .webp | image/webp |
| .html / .htm | text/html |
Upload Methods
- Local file (
--file): Reads file from disk, base64-encodes, sends inline - Remote URL (
--file-url): Passes URL to the API for server-side fetch - Pre-encoded base64 (
--file-b64): Reads raw base64 from disk with--file-b64-name
At least one file source is required for documents create and templates create.
Pagination
List commands support pagination via --page and --per-page. For documents and templates, --limit is an alias for --per-page. Document list page size must be between 1 and 50.
Single Page
sw documents list --page 2 --per-page 50
sw documents list --limit 30 --page 2
sw templates list --page 2 --per-page 50
sw templates list --limit 30 --page 2All Pages
sw documents list --allIn JSON mode, --all streams results as NDJSON (one JSON object per line). In human mode, all results are collected and displayed in a single table.
The paginator calls the API repeatedly, incrementing the page number until all items are fetched. A progress callback updates the spinner text as pages are fetched.
Dependency Pinning & Security
Runtime and development dependencies are pinned to exact versions in package.json; do not use semver ranges like ^ or ~. package-lock.json pins the full resolved dependency tree for local and CI installs.
Use npm ci for normal setup and CI. When intentionally upgrading a dependency, update the exact version and lockfile together, review the diff, and run npm audit before publishing or releasing. The overrides block pins targeted transitive dependencies when a security fix is needed before upstream packages have moved.
Development
Setup
git clone https://github.com/ziptied/Signwell-cli.git
cd Signwell-cli
npm ciScripts
npm run build # Build with tsup
npm run dev # Watch mode (rebuild on change)
npm test # Run tests with vitest
npm run typecheck # TypeScript type checking
npm run lint # TypeScript type checking (alias for typecheck)Project Structure
skills/
signwell-cli/
SKILL.md # Agent Skills spec — teaches AI agents to use `sw`
src/
index.ts # CLI entry point (yargs root)
api/
client.ts # Axios client with retry, auth, interceptors
documents.ts # Document API functions
templates.ts # Template API functions
bulk-send.ts # Bulk send API functions
webhooks.ts # Webhook API functions
me.ts # Account API function
commands/
auth.ts # auth login/logout/status
me.ts # me
profile.ts # profile list/add/use/remove/show
documents.ts # documents create/get/list/send/remind/download/delete/recipients
templates.ts # templates create/get/list/update/delete/use
bulk-send.ts # bulk-send create/get/list/documents/csv-template/validate
webhooks.ts # webhooks list/create/delete/listen
schema.ts # schema introspection
skills.ts # skills install (Agent Skills installer)
lib/
config.ts # Multi-profile config management
output.ts # Output formatting (tables, JSON, spinners)
errors.ts # Error classes and HTTP mapping
pagination.ts # Generic async paginator
upload.ts # File resolution (local/URL/base64)
csv.ts # CSV parsing
types/
api.ts # TypeScript type definitions
test/
lib/ # Unit tests for lib modules
commands/ # Integration tests for API modules (nock)
fixtures/ # JSON fixtures for test mockingTesting
Tests use Vitest with nock for HTTP mocking.
# Run all tests
npm test
# Run with coverage
npx vitest run --coverage
# Run a specific test file
npx vitest run test/lib/config.test.ts