Claude-skill-registry erpnext-errors-api
Error handling patterns for ERPNext/Frappe API development (v14/v15/v16). Covers whitelisted method errors, REST API errors, client-side handling, external integrations, and webhooks. Triggers: API error, whitelisted method error, frappe.call error, REST API error, webhook error, external API error, HTTP status codes.
install
source · Clone the upstream repo
git clone https://github.com/majiayu000/claude-skill-registry
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/majiayu000/claude-skill-registry "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/data/erpnext-errors-api" ~/.claude/skills/majiayu000-claude-skill-registry-erpnext-errors-api && rm -rf "$T"
manifest:
skills/data/erpnext-errors-api/SKILL.mdsource content
ERPNext API Error Handling
Patterns for handling errors in API development. For syntax details, see
erpnext-api-patterns.
Version: v14/v15/v16 compatible
API Error Handling Overview
┌─────────────────────────────────────────────────────────────────────┐ │ API ERROR HANDLING DECISION │ ├─────────────────────────────────────────────────────────────────────┤ │ │ │ Where is the error occurring? │ │ │ │ Server-side (Python)? │ │ ├── Validation error → frappe.throw() with clear message │ │ ├── Permission error → frappe.throw() + PermissionError │ │ ├── Not found → frappe.throw() + DoesNotExistError │ │ └── Unexpected → Log + generic error to client │ │ │ │ Client-side (JavaScript)? │ │ ├── frappe.call → Use error callback or .catch() │ │ └── frappe.xcall → Use try/catch with async/await │ │ │ │ External integration? │ │ └── requests library → try/except with specific exceptions │ │ │ └─────────────────────────────────────────────────────────────────────┘
HTTP Status Codes Reference
| Code | Meaning | When Frappe Uses |
|---|---|---|
| 200 | Success | Normal response |
| 400 | Bad Request | Validation error |
| 403 | Forbidden | Permission denied |
| 404 | Not Found | Document doesn't exist |
| 417 | Expectation Failed | frappe.throw() called |
| 500 | Server Error | Unhandled exception |
Server-Side Patterns
Basic Whitelisted Method
@frappe.whitelist() def update_status(docname, status): # Validate input if not docname: frappe.throw(_("Document name is required"), frappe.ValidationError) if status not in ["Draft", "Submitted", "Cancelled"]: frappe.throw(_("Invalid status: {0}").format(status)) try: doc = frappe.get_doc("My DocType", docname) doc.status = status doc.save() return {"success": True, "name": doc.name} except frappe.DoesNotExistError: frappe.throw(_("Document {0} not found").format(docname)) except frappe.PermissionError: frappe.throw(_("Permission denied"), frappe.PermissionError)
Bulk Operation with Partial Failure
@frappe.whitelist() def bulk_update(items): items = frappe.parse_json(items) results = {"success": [], "failed": []} for item in items: try: doc = frappe.get_doc("Item", item["name"]) doc.update(item) doc.save() results["success"].append(item["name"]) except Exception as e: results["failed"].append({ "name": item["name"], "error": str(e) }) frappe.db.commit() return results
Client-Side Patterns
frappe.call Error Handling
frappe.call({ method: "myapp.api.update_status", args: { docname: "DOC-001", status: "Submitted" }, callback: function(r) { if (r.message && r.message.success) { frappe.show_alert({message: __("Updated"), indicator: "green"}); } }, error: function(r) { // Called on HTTP error or frappe.throw frappe.msgprint({ title: __("Error"), message: r.message || __("Operation failed"), indicator: "red" }); } });
async/await Pattern
async function updateDocument(docname, status) { try { const result = await frappe.xcall("myapp.api.update_status", { docname: docname, status: status }); return result; } catch (error) { console.error("API Error:", error); frappe.throw(__("Failed to update document")); } }
External API Pattern
import requests def call_external_api(endpoint, data): try: response = requests.post( endpoint, json=data, timeout=30, headers={"Authorization": f"Bearer {get_api_key()}"} ) response.raise_for_status() return response.json() except requests.Timeout: frappe.log_error("External API timeout", "API Integration") frappe.throw(_("External service timeout. Please try again.")) except requests.HTTPError as e: frappe.log_error(f"HTTP {e.response.status_code}", "API Integration") frappe.throw(_("External service error")) except requests.RequestException as e: frappe.log_error(str(e), "API Integration") frappe.throw(_("Connection failed"))
Critical Rules
✅ ALWAYS
- Validate input before processing
- Use
for user-facing errorsfrappe.throw() - Log unexpected errors with
frappe.log_error() - Return structured responses from APIs
- Handle both success and error in callbacks
❌ NEVER
- Expose internal error details to users
- Catch exceptions without logging
- Return raw exception messages
- Assume API calls will succeed
- Skip input validation
Quick Reference: Error Responses
# User-facing error (shows alert) frappe.throw(_("Clear error message")) # Permission error (403) frappe.throw(_("Not allowed"), frappe.PermissionError) # Validation error (400) frappe.throw(_("Invalid input"), frappe.ValidationError) # Log error (no user message) frappe.log_error(frappe.get_traceback(), "Error Title")
Reference Files
| File | Contents |
|---|---|
| patterns.md | Detailed error handling patterns |
| examples.md | Complete working examples |
| anti-patterns.md | Common mistakes to avoid |
See Also
- API implementation patternserpnext-api-patterns
- Whitelisted method syntaxerpnext-syntax-whitelisted
- Server Script error handlingerpnext-errors-serverscripts