AgentSkillsCN

erpnext-errors-api

ERPNext/Frappe API 开发(v14/v15/v16)中的错误处理模式。涵盖白名单方法错误、REST API 错误、客户端侧处理、外部集成与 Webhook。触发条件:API 错误、白名单方法错误、frappe.call 错误、REST API 错误、Webhook 错误、外部 API 错误、HTTP 状态码。

SKILL.md
--- frontmatter
name: erpnext-errors-api
description: "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."

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

code
┌─────────────────────────────────────────────────────────────────────┐
│ 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

CodeMeaningWhen Frappe Uses
200SuccessNormal response
400Bad RequestValidation error
403ForbiddenPermission denied
404Not FoundDocument doesn't exist
417Expectation Failedfrappe.throw() called
500Server ErrorUnhandled exception

Server-Side Patterns

Basic Whitelisted Method

python
@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

python
@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

javascript
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

javascript
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

python
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

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

FileContents
patterns.mdDetailed error handling patterns
examples.mdComplete working examples
anti-patterns.mdCommon mistakes to avoid

See Also

  • erpnext-api-patterns - API implementation patterns
  • erpnext-syntax-whitelisted - Whitelisted method syntax
  • erpnext-errors-serverscripts - Server Script error handling