pandadoc
PandaDoc — manage documents, templates, contacts, and e-signatures via REST API
Packaged view
This page reorganizes the original catalog entry around fit, installability, and workflow context first. The original raw source lives below.
Install command
npx @skill-hub/cli install openclaw-skills-pandadoc
Repository
Skill path: skills/aiwithabidi/pandadoc
PandaDoc — manage documents, templates, contacts, and e-signatures via REST API
Open repositoryBest for
Primary workflow: Ship Full Stack.
Technical facets: Full Stack, Backend.
Target audience: everyone.
License: MIT.
Original source
Catalog source: SkillHub Club.
Repository owner: openclaw.
This is still a mirrored public skill entry. Review the repository before installing into production workflows.
What it helps with
- Install pandadoc into Claude Code, Codex CLI, Gemini CLI, or OpenCode workflows
- Review https://github.com/openclaw/skills before adding pandadoc to shared team environments
- Use pandadoc for development workflows
Works across
Favorites: 0.
Sub-skills: 0.
Aggregator: No.
Original source / Raw SKILL.md
---
name: pandadoc
description: "PandaDoc — manage documents, templates, contacts, and e-signatures via REST API"
homepage: https://www.agxntsix.ai
license: MIT
compatibility: Python 3.10+ (stdlib only — no dependencies)
metadata: {"openclaw": {"emoji": "🐼", "requires": {"env": ["PANDADOC_API_KEY"]}, "primaryEnv": "PANDADOC_API_KEY", "homepage": "https://www.agxntsix.ai"}}
---
# 🐼 PandaDoc
PandaDoc — manage documents, templates, contacts, and e-signatures via REST API
## Requirements
| Variable | Required | Description |
|----------|----------|-------------|
| `PANDADOC_API_KEY` | ✅ | API key from app.pandadoc.com |
## Quick Start
```bash
# List documents
python3 {{baseDir}}/scripts/pandadoc.py documents --status <value> --q <value> --tag <value>
# Get document details
python3 {{baseDir}}/scripts/pandadoc.py document-get id <value>
# Create document
python3 {{baseDir}}/scripts/pandadoc.py document-create --name <value> --template_uuid <value> --recipients <value>
# Send document
python3 {{baseDir}}/scripts/pandadoc.py document-send id <value> --message <value> --subject <value>
# Get status
python3 {{baseDir}}/scripts/pandadoc.py document-status id <value>
# Delete document
python3 {{baseDir}}/scripts/pandadoc.py document-delete id <value>
# Create sharing link
python3 {{baseDir}}/scripts/pandadoc.py document-link id <value> --recipient <value>
# List templates
python3 {{baseDir}}/scripts/pandadoc.py templates --q <value>
```
## All Commands
| Command | Description |
|---------|-------------|
| `documents` | List documents |
| `document-get` | Get document details |
| `document-create` | Create document |
| `document-send` | Send document |
| `document-status` | Get status |
| `document-delete` | Delete document |
| `document-link` | Create sharing link |
| `templates` | List templates |
| `template-get` | Get template |
| `contacts` | List contacts |
| `contact-create` | Create contact |
| `folders` | List folders |
| `webhooks` | List webhooks |
## Output Format
All commands output JSON by default. Add `--human` for readable formatted output.
```bash
python3 {{baseDir}}/scripts/pandadoc.py <command> --human
```
## Script Reference
| Script | Description |
|--------|-------------|
| `{{baseDir}}/scripts/pandadoc.py` | Main CLI — all commands in one tool |
## Credits
Built by [M. Abidi](https://www.linkedin.com/in/mohammad-ali-abidi) | [agxntsix.ai](https://www.agxntsix.ai)
[YouTube](https://youtube.com/@aiwithabidi) | [GitHub](https://github.com/aiwithabidi)
Part of the **AgxntSix Skill Suite** for OpenClaw agents.
📅 **Need help setting up OpenClaw for your business?** [Book a free consultation](https://cal.com/agxntsix/abidi-openclaw)
---
## Skill Companion Files
> Additional files collected from the skill directory layout.
### _meta.json
```json
{
"owner": "aiwithabidi",
"slug": "pandadoc",
"displayName": "Pandadoc",
"latest": {
"version": "1.0.0",
"publishedAt": 1772765547430,
"commit": "https://github.com/openclaw/skills/commit/8b61a92e13142c367ebaa360204893b61dec28b4"
},
"history": []
}
```
### scripts/pandadoc.py
```python
#!/usr/bin/env python3
"""PandaDoc CLI — PandaDoc — manage documents, templates, contacts, and e-signatures via REST API
Zero dependencies beyond Python stdlib.
Built by M. Abidi | agxntsix.ai
"""
import argparse, json, os, sys, urllib.request, urllib.error, urllib.parse
API_BASE = "https://api.pandadoc.com/public/v1"
def get_env(name):
val = os.environ.get(name, "")
if not val:
env_path = os.path.join(os.environ.get("WORKSPACE", os.path.expanduser("~/.openclaw/workspace")), ".env")
if os.path.exists(env_path):
with open(env_path) as f:
for line in f:
line = line.strip()
if line.startswith(name + "="):
val = line.split("=", 1)[1].strip().strip('"').strip("'")
break
if not val:
print(f"Error: {name} not set", file=sys.stderr)
sys.exit(1)
return val
def get_headers():
token = get_env("PANDADOC_API_KEY")
return {"Authorization": f"Bearer {token}", "Content-Type": "application/json", "Accept": "application/json"}
def get_api_base():
base = API_BASE
pass
return base
def req(method, path, data=None, params=None):
headers = get_headers()
if path.startswith("http"):
url = path
else:
url = get_api_base() + path
if params:
qs = urllib.parse.urlencode({k: v for k, v in params.items() if v is not None})
if qs:
url = f"{url}?{qs}" if "?" not in url else f"{url}&{qs}"
body = json.dumps(data).encode() if data else None
r = urllib.request.Request(url, data=body, method=method)
for k, v in headers.items():
r.add_header(k, v)
try:
resp = urllib.request.urlopen(r, timeout=30)
raw = resp.read().decode()
return json.loads(raw) if raw.strip() else {"ok": True}
except urllib.error.HTTPError as e:
err_body = e.read().decode()
print(json.dumps({"error": True, "code": e.code, "message": err_body}), file=sys.stderr)
sys.exit(1)
def try_json(val):
if val is None:
return None
try:
return json.loads(val)
except (json.JSONDecodeError, ValueError):
return val
def out(data, human=False):
if human and isinstance(data, dict):
for k, v in data.items():
print(f" {k}: {v}")
elif human and isinstance(data, list):
for item in data:
if isinstance(item, dict):
for k, v in item.items():
print(f" {k}: {v}")
print()
else:
print(item)
else:
print(json.dumps(data, indent=2, default=str))
def cmd_documents(args):
"""List documents."""
path = "/documents"
params = {}
if getattr(args, "status", None): params["status"] = args.status
if getattr(args, "q", None): params["q"] = args.q
if getattr(args, "tag", None): params["tag"] = args.tag
data = req("GET", path, params=params)
out(data, getattr(args, "human", False))
def cmd_document_get(args):
"""Get document details."""
path = f"/documents/{args.id}/details"
data = req("GET", path)
out(data, getattr(args, "human", False))
def cmd_document_create(args):
"""Create document."""
path = "/documents"
body = {}
if getattr(args, "name", None): body["name"] = try_json(args.name)
if getattr(args, "template_uuid", None): body["template_uuid"] = try_json(args.template_uuid)
if getattr(args, "recipients", None): body["recipients"] = try_json(args.recipients)
data = req("POST", path, data=body)
out(data, getattr(args, "human", False))
def cmd_document_send(args):
"""Send document."""
path = f"/documents/{args.id}/send"
body = {}
if getattr(args, "message", None): body["message"] = try_json(args.message)
if getattr(args, "subject", None): body["subject"] = try_json(args.subject)
data = req("POST", path, data=body)
out(data, getattr(args, "human", False))
def cmd_document_status(args):
"""Get status."""
path = f"/documents/{args.id}"
data = req("GET", path)
out(data, getattr(args, "human", False))
def cmd_document_delete(args):
"""Delete document."""
path = f"/documents/{args.id}"
data = req("DELETE", path)
out(data, getattr(args, "human", False))
def cmd_document_link(args):
"""Create sharing link."""
path = f"/documents/{args.id}/session"
body = {}
if getattr(args, "recipient", None): body["recipient"] = try_json(args.recipient)
data = req("POST", path, data=body)
out(data, getattr(args, "human", False))
def cmd_templates(args):
"""List templates."""
path = "/templates"
params = {}
if getattr(args, "q", None): params["q"] = args.q
data = req("GET", path, params=params)
out(data, getattr(args, "human", False))
def cmd_template_get(args):
"""Get template."""
path = f"/templates/{args.id}/details"
data = req("GET", path)
out(data, getattr(args, "human", False))
def cmd_contacts(args):
"""List contacts."""
path = "/contacts"
data = req("GET", path)
out(data, getattr(args, "human", False))
def cmd_contact_create(args):
"""Create contact."""
path = "/contacts"
body = {}
if getattr(args, "email", None): body["email"] = try_json(args.email)
if getattr(args, "first_name", None): body["first_name"] = try_json(args.first_name)
if getattr(args, "last_name", None): body["last_name"] = try_json(args.last_name)
data = req("POST", path, data=body)
out(data, getattr(args, "human", False))
def cmd_folders(args):
"""List folders."""
path = "/documents/folders"
data = req("GET", path)
out(data, getattr(args, "human", False))
def cmd_webhooks(args):
"""List webhooks."""
path = "/webhooks"
data = req("GET", path)
out(data, getattr(args, "human", False))
def main():
parser = argparse.ArgumentParser(description="PandaDoc CLI")
parser.add_argument("--human", action="store_true", help="Human-readable output")
sub = parser.add_subparsers(dest="command")
documents_p = sub.add_parser("documents", help="List documents")
documents_p.add_argument("--status", help="Status filter", default=None)
documents_p.add_argument("--q", help="Search", default=None)
documents_p.add_argument("--tag", help="Tag filter", default=None)
documents_p.set_defaults(func=cmd_documents)
document_get_p = sub.add_parser("document-get", help="Get document details")
document_get_p.add_argument("id", help="Document ID")
document_get_p.set_defaults(func=cmd_document_get)
document_create_p = sub.add_parser("document-create", help="Create document")
document_create_p.add_argument("--name", help="Name", default=None)
document_create_p.add_argument("--template_uuid", help="Template UUID", default=None)
document_create_p.add_argument("--recipients", help="JSON recipients", default=None)
document_create_p.set_defaults(func=cmd_document_create)
document_send_p = sub.add_parser("document-send", help="Send document")
document_send_p.add_argument("id", help="ID")
document_send_p.add_argument("--message", help="Message", default=None)
document_send_p.add_argument("--subject", help="Subject", default=None)
document_send_p.set_defaults(func=cmd_document_send)
document_status_p = sub.add_parser("document-status", help="Get status")
document_status_p.add_argument("id", help="ID")
document_status_p.set_defaults(func=cmd_document_status)
document_delete_p = sub.add_parser("document-delete", help="Delete document")
document_delete_p.add_argument("id", help="ID")
document_delete_p.set_defaults(func=cmd_document_delete)
document_link_p = sub.add_parser("document-link", help="Create sharing link")
document_link_p.add_argument("id", help="ID")
document_link_p.add_argument("--recipient", help="Recipient email", default=None)
document_link_p.set_defaults(func=cmd_document_link)
templates_p = sub.add_parser("templates", help="List templates")
templates_p.add_argument("--q", help="Search", default=None)
templates_p.set_defaults(func=cmd_templates)
template_get_p = sub.add_parser("template-get", help="Get template")
template_get_p.add_argument("id", help="Template ID")
template_get_p.set_defaults(func=cmd_template_get)
contacts_p = sub.add_parser("contacts", help="List contacts")
contacts_p.set_defaults(func=cmd_contacts)
contact_create_p = sub.add_parser("contact-create", help="Create contact")
contact_create_p.add_argument("--email", help="Email", default=None)
contact_create_p.add_argument("--first_name", help="First name", default=None)
contact_create_p.add_argument("--last_name", help="Last name", default=None)
contact_create_p.set_defaults(func=cmd_contact_create)
folders_p = sub.add_parser("folders", help="List folders")
folders_p.set_defaults(func=cmd_folders)
webhooks_p = sub.add_parser("webhooks", help="List webhooks")
webhooks_p.set_defaults(func=cmd_webhooks)
args = parser.parse_args()
if not args.command:
parser.print_help()
sys.exit(1)
args.func(args)
if __name__ == "__main__":
main()
```