Files Generated:
1. API_ENDPOINTS.md (1,171 lines)
- Complete documentation of all 771 DocTypes in ERPNext
- Organized by 37 modules
- Sample record names for each DocType
- Full REST endpoint patterns
- Examples and usage instructions
2. generate_api_docs.py
- Python script to auto-discover and document all API endpoints
- Fetches live data from your ERPNext instance
- Can be re-run anytime to update documentation
3. discover_api_endpoints.sh
- Bash alternative for API discovery
- Lightweight script for quick checks
4. test_api.sh
- Ready-to-use API testing script
- Demonstrates authentication and common API calls
Key Features of the Documentation:
Complete Coverage:
- ✅ All 771 DocTypes documented
- ✅ 37 modules categorized
- ✅ Sample data for each DocType
- ✅ Single vs Standard vs Submittable types identified
- ✅ Child tables marked
API Endpoint Patterns for Each DocType:
# Standard DocTypes (e.g., Item, Customer)
GET /api/resource/{DocType} # List all
GET /api/resource/{DocType}/{name} # Get single
POST /api/resource/{DocType} # Create new
PUT /api/resource/{DocType}/{name} # Update
DELETE /api/resource/{DocType}/{name} # Delete
# Single DocTypes (e.g., System Settings)
GET /api/resource/{DocType}/{DocType} # Get singleton
Sample DocTypes by Category:
Standard DocTypes (most common):
- Customer, Item, Sales Order, Purchase Order, etc.
Single DocTypes (singletons):
- System Settings, Accounts Settings, Stock Settings, etc.
Submittable DocTypes (workflow):
- Sales Invoice, Purchase Invoice, Journal Entry, etc.
Child Tables (part of parent):
- Sales Order Item, Purchase Order Item, etc.
319 lines
11 KiB
Python
Executable File
319 lines
11 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
|
|
import requests
|
|
import json
|
|
from datetime import datetime
|
|
import urllib.parse
|
|
|
|
# Configuration
|
|
API_URL = "http://localhost:8080"
|
|
USERNAME = "Administrator"
|
|
PASSWORD = "LocalDev123!"
|
|
|
|
def login():
|
|
"""Login and return session"""
|
|
session = requests.Session()
|
|
response = session.post(
|
|
f"{API_URL}/api/method/login",
|
|
json={"usr": USERNAME, "pwd": PASSWORD}
|
|
)
|
|
if response.status_code == 200:
|
|
print("✓ Logged in successfully")
|
|
return session
|
|
else:
|
|
print("✗ Login failed")
|
|
return None
|
|
|
|
def get_all_doctypes(session):
|
|
"""Fetch all DocTypes"""
|
|
response = session.get(
|
|
f"{API_URL}/api/resource/DocType",
|
|
params={
|
|
"limit_page_length": 1000,
|
|
"fields": json.dumps(["name", "module", "issingle", "is_submittable", "istable"])
|
|
}
|
|
)
|
|
if response.status_code == 200:
|
|
return response.json().get("data", [])
|
|
return []
|
|
|
|
def get_sample_records(session, doctype, limit=3):
|
|
"""Get sample record names for a DocType"""
|
|
try:
|
|
# URL encode the DocType name
|
|
doctype_encoded = urllib.parse.quote(doctype)
|
|
response = session.get(
|
|
f"{API_URL}/api/resource/{doctype_encoded}",
|
|
params={
|
|
"limit_page_length": limit,
|
|
"fields": json.dumps(["name"])
|
|
}
|
|
)
|
|
if response.status_code == 200:
|
|
data = response.json().get("data", [])
|
|
return [record.get("name", "") for record in data]
|
|
except:
|
|
pass
|
|
return []
|
|
|
|
def generate_documentation(session):
|
|
"""Generate comprehensive API documentation"""
|
|
|
|
print("Fetching all DocTypes...")
|
|
doctypes = get_all_doctypes(session)
|
|
print(f"Found {len(doctypes)} DocTypes")
|
|
|
|
# Group by module
|
|
modules = {}
|
|
for doctype in doctypes:
|
|
module = doctype.get("module", "Unknown")
|
|
if module not in modules:
|
|
modules[module] = []
|
|
modules[module].append(doctype)
|
|
|
|
# Sort modules
|
|
sorted_modules = sorted(modules.keys())
|
|
|
|
# Generate markdown
|
|
doc = []
|
|
doc.append("# ERPNext API Endpoints Documentation")
|
|
doc.append("")
|
|
doc.append(f"Generated on: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
|
|
doc.append("")
|
|
doc.append("This document provides a comprehensive list of all available API endpoints in ERPNext.")
|
|
doc.append("")
|
|
|
|
# Authentication section
|
|
doc.append("## Authentication")
|
|
doc.append("")
|
|
doc.append("All API calls require authentication. First, login to get a session:")
|
|
doc.append("")
|
|
doc.append("```bash")
|
|
doc.append("curl -c cookies.txt -X POST \\")
|
|
doc.append(" -H \"Content-Type: application/json\" \\")
|
|
doc.append(" -d '{\"usr\":\"Administrator\",\"pwd\":\"your_password\"}' \\")
|
|
doc.append(" http://localhost:8080/api/method/login")
|
|
doc.append("```")
|
|
doc.append("")
|
|
|
|
# API Pattern section
|
|
doc.append("## API Endpoint Pattern")
|
|
doc.append("")
|
|
doc.append("All DocTypes follow the same RESTful pattern:")
|
|
doc.append("")
|
|
doc.append("- **List/Search**: `GET /api/resource/{DocType}`")
|
|
doc.append("- **Get Single**: `GET /api/resource/{DocType}/{name}`")
|
|
doc.append("- **Create**: `POST /api/resource/{DocType}`")
|
|
doc.append("- **Update**: `PUT /api/resource/{DocType}/{name}`")
|
|
doc.append("- **Delete**: `DELETE /api/resource/{DocType}/{name}`")
|
|
doc.append("")
|
|
|
|
# Table of Contents
|
|
doc.append("## Table of Contents")
|
|
doc.append("")
|
|
for module in sorted_modules:
|
|
doc.append(f"- [{module}](#{module.lower().replace(' ', '-')})")
|
|
doc.append("")
|
|
|
|
# DocTypes by Module
|
|
doc.append("## Available DocTypes by Module")
|
|
doc.append("")
|
|
|
|
total_processed = 0
|
|
for module in sorted_modules:
|
|
print(f"\nProcessing module: {module}")
|
|
doc.append(f"### {module}")
|
|
doc.append("")
|
|
doc.append("| DocType | Type | Sample Names | API Endpoints |")
|
|
doc.append("|---------|------|--------------|---------------|")
|
|
|
|
module_doctypes = sorted(modules[module], key=lambda x: x.get("name", ""))
|
|
|
|
for doctype_info in module_doctypes:
|
|
doctype = doctype_info.get("name", "")
|
|
issingle = doctype_info.get("issingle", 0)
|
|
istable = doctype_info.get("istable", 0)
|
|
is_submittable = doctype_info.get("is_submittable", 0)
|
|
|
|
# Determine type
|
|
if issingle:
|
|
dtype = "Single"
|
|
elif istable:
|
|
dtype = "Child Table"
|
|
elif is_submittable:
|
|
dtype = "Submittable"
|
|
else:
|
|
dtype = "Standard"
|
|
|
|
# Get sample records
|
|
if issingle:
|
|
samples = [doctype]
|
|
elif istable:
|
|
samples = ["(Child of parent doc)"]
|
|
else:
|
|
samples = get_sample_records(session, doctype)
|
|
if not samples:
|
|
samples = ["(No records)"]
|
|
|
|
# Format samples
|
|
sample_str = ", ".join(str(s) for s in samples[:2])
|
|
if len(sample_str) > 40:
|
|
sample_str = sample_str[:37] + "..."
|
|
|
|
# Format endpoints
|
|
doctype_url = urllib.parse.quote(doctype)
|
|
if issingle:
|
|
endpoints = f"`GET /api/resource/{doctype_url}/{doctype_url}`"
|
|
else:
|
|
endpoints = f"`/api/resource/{doctype_url}`"
|
|
|
|
doc.append(f"| {doctype} | {dtype} | {sample_str} | {endpoints} |")
|
|
|
|
total_processed += 1
|
|
if total_processed % 10 == 0:
|
|
print(f" Processed {total_processed} DocTypes...")
|
|
|
|
doc.append("")
|
|
|
|
print(f"\nProcessed {total_processed} DocTypes total")
|
|
|
|
# Common Query Parameters
|
|
doc.append("## Common Query Parameters")
|
|
doc.append("")
|
|
doc.append("### For List Endpoints")
|
|
doc.append("")
|
|
doc.append("| Parameter | Description | Example |")
|
|
doc.append("|-----------|-------------|---------|")
|
|
doc.append("| `fields` | Select specific fields | `fields=[\"name\",\"status\"]` |")
|
|
doc.append("| `filters` | Filter results | `filters=[[\"status\",\"=\",\"Active\"]]` |")
|
|
doc.append("| `limit_start` | Pagination offset | `limit_start=0` |")
|
|
doc.append("| `limit_page_length` | Page size | `limit_page_length=20` |")
|
|
doc.append("| `order_by` | Sort results | `order_by=modified desc` |")
|
|
doc.append("")
|
|
|
|
# Examples section
|
|
doc.append("## Examples")
|
|
doc.append("")
|
|
doc.append("### Get list with filters")
|
|
doc.append("```bash")
|
|
doc.append("curl -b cookies.txt \\")
|
|
doc.append(" \"http://localhost:8080/api/resource/Item?filters=[[\\\"item_group\\\",\\\"=\\\",\\\"Products\\\"]]&fields=[\\\"item_code\\\",\\\"item_name\\\"]\"")
|
|
doc.append("```")
|
|
doc.append("")
|
|
doc.append("### Get single document")
|
|
doc.append("```bash")
|
|
doc.append("curl -b cookies.txt \\")
|
|
doc.append(" \"http://localhost:8080/api/resource/User/Administrator\"")
|
|
doc.append("```")
|
|
doc.append("")
|
|
doc.append("### Create new document")
|
|
doc.append("```bash")
|
|
doc.append("curl -b cookies.txt -X POST \\")
|
|
doc.append(" -H \"Content-Type: application/json\" \\")
|
|
doc.append(" -d '{\"doctype\":\"Item\",\"item_code\":\"TEST-001\",\"item_name\":\"Test Item\",\"item_group\":\"Products\",\"stock_uom\":\"Nos\"}' \\")
|
|
doc.append(" \"http://localhost:8080/api/resource/Item\"")
|
|
doc.append("```")
|
|
doc.append("")
|
|
doc.append("### Update document")
|
|
doc.append("```bash")
|
|
doc.append("curl -b cookies.txt -X PUT \\")
|
|
doc.append(" -H \"Content-Type: application/json\" \\")
|
|
doc.append(" -d '{\"item_name\":\"Updated Test Item\"}' \\")
|
|
doc.append(" \"http://localhost:8080/api/resource/Item/TEST-001\"")
|
|
doc.append("```")
|
|
doc.append("")
|
|
doc.append("### Delete document")
|
|
doc.append("```bash")
|
|
doc.append("curl -b cookies.txt -X DELETE \\")
|
|
doc.append(" \"http://localhost:8080/api/resource/Item/TEST-001\"")
|
|
doc.append("```")
|
|
doc.append("")
|
|
|
|
# Special Endpoints
|
|
doc.append("## Special Endpoints")
|
|
doc.append("")
|
|
doc.append("### Authentication")
|
|
doc.append("- `POST /api/method/login` - Login")
|
|
doc.append("- `POST /api/method/logout` - Logout")
|
|
doc.append("")
|
|
doc.append("### File Operations")
|
|
doc.append("- `POST /api/method/upload_file` - Upload files")
|
|
doc.append("- `GET /api/method/download_file` - Download files")
|
|
doc.append("")
|
|
doc.append("### Reports")
|
|
doc.append("- `GET /api/method/frappe.desk.query_report.run` - Run reports")
|
|
doc.append("")
|
|
|
|
# DocType Categories
|
|
doc.append("## DocType Categories")
|
|
doc.append("")
|
|
doc.append("### Single DocTypes")
|
|
doc.append("These DocTypes have only one record (singleton pattern):")
|
|
doc.append("")
|
|
single_docs = [d.get("name") for d in doctypes if d.get("issingle")]
|
|
for doc_name in sorted(single_docs)[:20]: # Show first 20
|
|
doc.append(f"- {doc_name}")
|
|
if len(single_docs) > 20:
|
|
doc.append(f"- ... and {len(single_docs) - 20} more")
|
|
doc.append("")
|
|
|
|
doc.append("### Child Tables")
|
|
doc.append("These DocTypes are child tables and cannot be accessed directly:")
|
|
doc.append("")
|
|
child_docs = [d.get("name") for d in doctypes if d.get("istable")]
|
|
for doc_name in sorted(child_docs)[:20]: # Show first 20
|
|
doc.append(f"- {doc_name}")
|
|
if len(child_docs) > 20:
|
|
doc.append(f"- ... and {len(child_docs) - 20} more")
|
|
doc.append("")
|
|
|
|
doc.append("### Submittable DocTypes")
|
|
doc.append("These DocTypes support document submission workflow:")
|
|
doc.append("")
|
|
submit_docs = [d.get("name") for d in doctypes if d.get("is_submittable")]
|
|
for doc_name in sorted(submit_docs)[:20]: # Show first 20
|
|
doc.append(f"- {doc_name}")
|
|
if len(submit_docs) > 20:
|
|
doc.append(f"- ... and {len(submit_docs) - 20} more")
|
|
doc.append("")
|
|
|
|
# Notes
|
|
doc.append("## Important Notes")
|
|
doc.append("")
|
|
doc.append("1. **URL Encoding**: DocType names with spaces must be URL encoded (e.g., \"Sales Order\" → \"Sales%20Order\")")
|
|
doc.append("2. **Permissions**: Access to DocTypes depends on user permissions")
|
|
doc.append("3. **Rate Limiting**: Default rate limit is 60 requests per minute")
|
|
doc.append("4. **Single DocTypes**: Use the DocType name as both the resource and document name")
|
|
doc.append("5. **Child Tables**: Cannot be accessed directly, only through their parent document")
|
|
doc.append("6. **Submittable Documents**: Support additional states (Draft, Submitted, Cancelled)")
|
|
doc.append("")
|
|
|
|
return "\n".join(doc)
|
|
|
|
def main():
|
|
print("=" * 50)
|
|
print("ERPNext API Documentation Generator")
|
|
print("=" * 50)
|
|
print()
|
|
|
|
# Login
|
|
session = login()
|
|
if not session:
|
|
print("Failed to login!")
|
|
return
|
|
|
|
# Generate documentation
|
|
documentation = generate_documentation(session)
|
|
|
|
# Save to file
|
|
output_file = "API_ENDPOINTS.md"
|
|
with open(output_file, "w") as f:
|
|
f.write(documentation)
|
|
|
|
print()
|
|
print("=" * 50)
|
|
print(f"✓ Documentation saved to: {output_file}")
|
|
print("=" * 50)
|
|
|
|
if __name__ == "__main__":
|
|
main() |