Skip to content

Bidsketch/signwell-cli

Repository files navigation

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

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

Requires 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 "alice@example.com: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:bob@example.com: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 "client@company.com: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.pdf

HR: 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:hire@example.com:New Hire" \
  --text-tags

# Create a draft for a new hire, pre-filling the start date
sw templates use tmpl_offer \
  --recipient "New Hire:maria@gmail.com: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_abc123

Developer: 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 "test@example.com:Test User" \
  --name "Test Doc" \
  --test-mode

# Everything works the same, but no emails are delivered
sw documents list

Webhook 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 time

Multi-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 list

Global 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

  1. Explicit --api-key flag for auth setup commands.
  2. Config file profile selected by --profile <name> or the active profile.
  3. 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 Bidsketch/signwell-cli

Option 2: Built-in command

If you already have signwell-cli installed globally, the skill installer is built right in:

sw skills install

This will:

  1. Copy the bundled skills to the canonical ~/.agents/skills/ directory
  2. Auto-detect installed AI agents (Claude Code, Cursor, Copilot, etc.)
  3. Create symlinks from each agent's skill directory into the canonical location when possible
  4. Keep existing installs unchanged unless --force is passed

You can also target a specific agent:

sw skills install --agent claude-code
sw skills install --agent cursor

Option 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 --json mode 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 + metadata

Supported 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-mode

Options:

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": "john@example.com",
    "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 staging

After 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 production

JSON 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 --json

JSON output:

{
  "success": true,
  "data": {
    "id": "user_abc123",
    "role": "admin",
    "user": {
      "id": "user_abc123",
      "name": "John Doe",
      "email": "john@example.com"
    },
    "account": {
      "id": "acct_xyz",
      "name": "Acme Corp",
      "plan_tier": "Business"
    }
  },
  "error": null,
  "meta": {}
}

profile

Manage multiple configuration profiles.

sw profile list

sw profile list

Human output:

   Name        API Key           Test Mode
→  default     sk_l...c123       no
   staging     sk_t...xyz        yes

JSON 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 production

JSON 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 production

JSON 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 "alice@example.com:Alice Smith" \
  --name "Service Agreement"

# Multiple files and recipients as a draft
sw documents create \
  --file contract.pdf \
  --file appendix.pdf \
  --recipient "alice@example.com:Alice Smith" \
  --recipient "bob@example.com: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 "alice@example.com:Alice Smith" \
  --name "Tagged Service Agreement" \
  --text-tags \
  --send

# File from URL
sw documents create \
  --file-url "https://example.com/contract.pdf" \
  --recipient "alice@example.com:Alice"

# Embedded signing
sw documents create \
  --file contract.pdf \
  --recipient "alice@example.com:Alice:embedded" \
  --name "Embedded Doc"

# Draft with options
sw documents create \
  --file contract.pdf \
  --recipient "alice@example.com:Alice" \
  --text-tags \
  --signing-order \
  --expiration-days 30 \
  --reminder-days 3 7 14

Options:

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 address
  • name (optional): Display name
  • embedded (optional): Use embedded signing (returns embedded_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": "alice@example.com",
        "name": "Alice Smith",
        "status": "pending"
      }
    ]
  },
  "error": null,
  "meta": {}
}

Note: Documents are always created as drafts unless --send is also passed. Pass --text-tags --send to send immediately; the returned document will have status: pending and each recipient will include a signing_url.


sw documents get <id>

Get full document details.

sw documents get doc_abc123
sw documents get doc_abc123 --json

API: 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_abc123

API: POST /documents/{id}/send

JSON output: Updated Document object.


sw documents remind <id>

Send a reminder to all pending signers.

sw documents remind doc_abc123

API: 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 "old@email.com:new@email.com: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:client@example.com: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_abc123

API: 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:alice@example.com:Alice Smith" \
  --field "company=Acme Inc" \
  --field "date=2024-01-15"

# Simple recipient (no placeholder role)
sw templates use tmpl_abc123 \
  --recipient "bob@example.com: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:alice@example.com:Alice Smith — maps to a template placeholder role
  • alice@example.com: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_abc123

API: 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 list

API: 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 POST requests on any path
  • Validates HMAC-SHA256 signature via x-signwell-signature header 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 OK for valid events, 401 Unauthorized for invalid signatures, 405 for 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.create

Available 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.use

Note: sw schema outputs raw JSON; it is not wrapped in the standard --json envelope.


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:

  1. Copies skills to canonical ~/.agents/skills/ directory
  2. Detects installed AI agents on the system
  3. Creates symlinks from each agent's skills directory to the canonical location when possible
  4. Falls back to copying if symlinks are not supported (e.g., Windows without developer mode)
  5. Keeps existing installs unchanged unless --force is 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 list

JSON 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 --quiet

NDJSON 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'
done

JSON Envelope

All --json output follows this envelope format, with two exceptions:

  • sw schema <command> outputs raw JSON (not envelope-wrapped).
  • sw <command> --all --json streams 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 Requests
  • 503 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

  1. Local file (--file): Reads file from disk, base64-encodes, sends inline
  2. Remote URL (--file-url): Passes URL to the API for server-side fetch
  3. 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 2

All Pages

sw documents list --all

In 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/Bidsketch/signwell-cli.git
cd signwell-cli
npm ci

Scripts

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 mocking

Testing

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

Tech Stack

About

Official Node.js CLI for the SignWell eSignature API — send, manage, and download signed documents from your terminal. HIPAA, SOC 2, eIDAS compliant.

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors