erpnext-errors-api
npx machina-cli add skill OpenAEC-Foundation/ERPNext_Anthropic_Claude_Development_Skill_Package/erpnext-errors-api --openclawERPNext 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
frappe.throw()for user-facing errors - 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
erpnext-api-patterns- API implementation patternserpnext-syntax-whitelisted- Whitelisted method syntaxerpnext-errors-serverscripts- Server Script error handling
Source
git clone https://github.com/OpenAEC-Foundation/ERPNext_Anthropic_Claude_Development_Skill_Package/blob/main/skills/source/errors/erpnext-errors-api/SKILL.mdView on GitHub Overview
Defines error handling patterns for ERPNext/Frappe API development across v14–v16. Covers server-side validation, permissions, and not-found errors, plus client-side frappe.call/xcall handling, and external/webhook integrations. Emphasizes consistent HTTP status signaling and clear messages.
How This Skill Works
On the server, errors use frappe.throw with specific error types (ValidationError, PermissionError, DoesNotExistError) and descriptive messages. Client-side calls use error callbacks or try/catch with async/await for frappe.call and frappe.xcall, while external integrations rely on Python requests with targeted exception handling. HTTP status codes map to outcomes such as 400, 403, 404, 417, and 500 to standardize responses.
When to Use It
- Validating inputs and failing fast in server-side methods via whitelisted functions
- Handling permission and not-found errors with appropriate frappe.throw and exception types
- Processing bulk operations with per-item success and failure reporting
- Implementing client-side error handling for frappe.call and frappe.xcall
- Integrating with external APIs or webhooks using requests and corresponding HTTP status handling
Quick Start
- Step 1: Identify error points across server methods, client calls, and external integrations
- Step 2: Implement server-side error handling using specific exception types and clear messages
- Step 3: Add client-side error handling with frappe.call/xcall and standardize responses
Best Practices
- Use precise exception types (ValidationError, PermissionError, DoesNotExistError) with clear messages
- Validate inputs on the server side before database operations and fail fast
- Return consistent error payloads and avoid leaking internal details
- For bulk operations, accumulate per-item results and report partial failures
- Log unexpected errors and surface user-friendly messages to clients; map to appropriate HTTP statuses
Example Use Cases
- Server-side whitelisted method update_status validates inputs, handles missing doc, and returns success or descriptive errors
- Bulk update processes a list of items, collecting successful updates and listing per-item failures with reasons
- Client-side code using frappe.call handles errors via callback and checks r.message for success flags
- External API integration uses requests with try/except to handle timeouts, auth errors, and non-200 responses
- Webhook receiver validates payloads and uses appropriate error signaling to return 400 or 500 as needed