frappe-api-handler
Generate whitelisted API methods and REST endpoints for standard Frappe and microservices.
Codex または Claude でインストール この Prompt をコピーして Codex、Claude、または他のアシスタントに貼り付けると、Skill ページを確認してインストールできます。
メニュー
Generate whitelisted API methods and REST endpoints for standard Frappe and microservices.
Codex または Claude でインストール この Prompt をコピーして Codex、Claude、または他のアシスタントに貼り付けると、Skill ページを確認してインストールできます。
SOC 職業分類に基づく
Use when configuring hooks.py — doc_events, scheduler_events, override_whitelisted_methods, override_doctype_class, jinja, boot_session, permission_query_conditions, has_permission, and fixtures. Prevents silent hook failures from wrong paths or missing migrate after scheduler changes. Covers full hooks.py reference beyond document lifecycle events. Keywords: hooks.py, doc_events, scheduler_events, override method, permission query conditions, boot session, fixtures, override_doctype_class, extend_doctype_class.
Use when building or consuming Frappe REST APIs — auto /api/resource CRUD, @frappe.whitelist endpoints, token vs session auth, file uploads, and response handling. Prevents unauthorized exposure from missing permission checks on whitelisted methods. Covers /api/resource, /api/method, token auth, OAuth, file upload, error mapping. Keywords: whitelist API, /api/resource, /api/method, Frappe REST, token auth, api_key api_secret, call Frappe from outside, expand, filters.
Use when reading or writing Frappe data safely — get_doc, get_all, get_list, frappe.db.get_value, parameterized frappe.db.sql, transactions, bulk ops, and performance. Prevents SQL injection and permission leaks from wrong API choice. Covers ORM reads/writes, raw SQL, transactions, bulk_update, N+1 avoidance. Keywords: frappe.db.sql, frappe.get_all, get_value, set_value, bulk update, N+1, db transaction, get_list, SQL injection, parameterized query.
Use when implementing Frappe's permission model — roles, perm levels, user permissions, share, permission_query_conditions, has_permission hooks, and in-code checks. Prevents unauthorized access from missing server-side checks or broken row-level filters. Covers role permissions, field-level perm_level, User Permissions, share, hooks, frappe.has_permission. Keywords: permissions, role, user permission, perm level, restrict rows, frappe.has_permission, field level security, permission query conditions, share, PermissionError.
Use when debugging Frappe errors, using bench console for live inspection, analyzing tracebacks, or reading Frappe log files. Prevents wasted debugging time from ignoring log context, misreading tracebacks, and not using bench console effectively. Covers bench console, frappe.logger, error log DocType, traceback analysis, common error patterns, log file locations, pdb/debugger integration, VS Code DAP, profiling, Frappe Recorder, mariadb diagnostics. Keywords: debug, bench console, traceback, error log, frappe.logger, pdb, debugging, log analysis, inspect, VS Code, DAP, profiling, recorder, mariadb, monitor, ERPNext error, how to debug, find the bug, what went wrong, stack trace, error message..
Use when receiving vague or unclear ERPNext/Frappe development requests that need interpretation. Transforms requirements like 'make invoice auto-calculate' or 'add approval workflow' into concrete technical specifications. Determines which Frappe mechanisms to use and maps to the full 61-skill catalog. Keywords: vague requirement, clarify scope, translate business need, technical spec, implementation plan, what does this mean, unclear requirement, translate to code, how to build this.
| name | frappe-api-handler |
| description | Generate whitelisted API methods and REST endpoints for standard Frappe and microservices. |
Create secure API endpoints for Frappe applications. Supports both standard Frappe and microservices.
@frappe.whitelist()
def get_customer_details(customer_name):
if not frappe.has_permission("Customer", "read"):
frappe.throw(_("Not permitted"), frappe.PermissionError)
customer = frappe.get_doc("Customer", customer_name)
return {
"name": customer.name,
"customer_name": customer.customer_name,
"email": customer.email_id
}
@frappe.whitelist(allow_guest=True)
def public_api():
return {"message": "Public data"}
GET with pagination:
@frappe.whitelist()
def get_items(filters=None, limit=20, page=1):
filters = frappe.parse_json(filters) if isinstance(filters, str) else filters or {}
if not frappe.has_permission("Item", "read"):
frappe.throw(_("Not permitted"), frappe.PermissionError)
items = frappe.get_all("Item", filters=filters, limit=limit, limit_start=(page-1)*limit)
return {"items": items, "total": frappe.db.count("Item", filters=filters)}
POST:
@frappe.whitelist()
def create_order(order_data):
data = frappe.parse_json(order_data) if isinstance(order_data, str) else order_data
if not data.get("customer"):
frappe.throw(_("Customer is required"))
so = frappe.get_doc({"doctype": "Sales Order", **data})
so.insert()
return {"success": True, "name": so.name}
PUT/DELETE: Similar pattern - get doc, update/delete, return result
@app.secure_route('/api/customers', methods=['GET'])
def list_customers(user):
tenant_id = get_current_tenant_id()
app.set_tenant_id(tenant_id)
customers = app.tenant_db.get_all('Customer', filters=request.args.get('filters', {}))
return {"data": customers}
@app.secure_route('/api/customers', methods=['POST'])
def create_customer(user):
tenant_id = get_current_tenant_id()
app.set_tenant_id(tenant_id)
customer = app.tenant_db.insert_doc('Customer', request.json)
return {"success": True, "data": customer.as_dict()}, 201
@frappe.whitelist()
def api_with_errors(param):
try:
if not param:
frappe.throw(_("Required"), frappe.ValidationError)
return {"success": True, "data": process(param)}
except frappe.ValidationError as e:
return {"success": False, "error": str(e), "code": "VALIDATION_ERROR"}, 400
except frappe.PermissionError as e:
return {"success": False, "error": str(e), "code": "PERMISSION_ERROR"}, 403
except frappe.DoesNotExistError as e:
return {"success": False, "error": str(e), "code": "NOT_FOUND"}, 404
except Exception as e:
frappe.log_error(f"API error: {e}")
return {"success": False, "error": "Internal error", "code": "INTERNAL_ERROR"}, 500
Session-based:
@frappe.whitelist()
def authenticated_api():
return {"user": frappe.session.user}
API Key:
@frappe.whitelist()
def api_key_auth():
api_key = frappe.get_request_header("X-API-Key")
if not api_key:
frappe.throw(_("API Key required"), frappe.AuthenticationError)
# Validate key
return {"authenticated": True}
frappe.has_permission()frappe.parse_json()app.tenant_db in microservicesfrappe.log_error()@frappe.whitelist() decoratorfrappe._() for translatable messagesRemember: This skill is model-invoked. Claude will use it autonomously when detecting API development tasks.
Cross-package reference from frappe-core-api and frappe-syntax-whitelisted: where to expose logic, how clients call Frappe, and rules that extend the patterns above.
What do you need?
├── CRUD on documents (external client)
│ ├── v14: REST /api/resource/{doctype}
│ └── v15+: REST /api/v2/document/{doctype} (new) or /api/resource/ (still works)
│
├── Call custom server logic (external client)
│ └── RPC: POST /api/method/{dotted.path.to.function}
│
├── Notify external systems on document events
│ └── Webhooks (configured in UI or via DocType)
│
├── Client-side calls (JavaScript in Frappe desk)
│ ├── frappe.xcall() — async/await (RECOMMENDED)
│ └── frappe.call() — callback/promise pattern
│
└── Authentication method?
├── Server-to-server integration → Token Auth (RECOMMENDED)
├── Third-party app / mobile → OAuth 2.0
├── Browser session (short-lived) → Session/Cookie Auth
└── Quick scripting / testing → Token Auth
@frappe.whitelist() designWhat kind of endpoint?
|
+-- Standalone API (utility, integration, dashboard)?
| --> @frappe.whitelist() on a module-level function
| --> Call via: frappe.call('myapp.api.function')
| --> URL: /api/method/myapp.api.function
|
+-- Document-specific action?
| --> @frappe.whitelist() on a Document class method
| --> Call via: frm.call('method_name')
| --> URL: /api/method/run_doc_method (internal)
|
+-- Server Script (no-code)?
--> Use Server Script DocType instead (no decorator needed)
Who may call the API?
|
+-- Anyone (including guests)?
| --> allow_guest=True + thorough input validation + rate limiting
|
+-- Logged-in users only?
+-- Specific role? --> frappe.only_for("RoleName")
+-- DocType-level? --> frappe.has_permission(doctype, ptype, throw=True)
+-- Document-level? --> frappe.has_permission(doctype, ptype, doc, throw=True)
Which HTTP methods?
|
+-- Read only? --> methods=["GET"]
+-- Write only? --> methods=["POST"]
+-- Both? --> methods=["GET","POST"] or default
Module-level RPC / Desk entrypoint: /api/method/{dotted.module.path.function_name} (returns JSON with message carrying the Python return value on success.)
| RPC decorator option | Effect | Notes |
|---|---|---|
allow_guest=True | No login required | ALWAYS tighten validation + consider @rate_limit |
xss_safe=True | Skips XSS escaping | NEVER without fully trusted/sanitized output |
methods=[...] | Limits HTTP verbs | e.g. ["POST"] for mutations |
force_types=True | [v15+] Require param annotations | Missing annotations → FrappeTypeError |
| REST operation | Method | v14 | v15+ (v2) |
|---|---|---|---|
| List | GET | /api/resource/{doctype} | /api/v2/document/{doctype} |
| Create | POST | /api/resource/{doctype} | /api/v2/document/{doctype} |
| Read | GET | /api/resource/{doctype}/{name} | /api/v2/document/{doctype}/{name} |
| Update | PUT | /api/resource/{doctype}/{name} | PATCH /api/v2/document/{doctype}/{name} |
| Delete | DELETE | /api/resource/{doctype}/{name} | DELETE /api/v2/document/{doctype}/{name} |
| Code | Typical meaning |
|---|---|
| 200 | Success |
| 400 | Bad request / validation |
| 401 | Not authenticated |
| 403 | Authenticated but not permitted |
| 404 | Missing doc/resource |
| 417 | frappe.throw / expectation failed |
| 429 | Rate limited |
| 500 | Unhandled server error |
frappe.client RPC (Desk / token) | Purpose |
|---|---|
get_value, get_list, get | Read |
insert, save, submit, cancel, delete | Writes / workflow |
get_count | Count with filters |
List query params clients often send: fields, filters, or_filters, order_by, limit_start, limit_page_length (limit alias on v15+), optional debug.
Accept: application/json when calling Frappe REST from scripts or services (otherwise responses may be HTML).timeout=30) on outbound requests to third parties.frappe.conf or environment variables — NEVER hardcode keys in repo.X-Frappe-Webhook-Signature) when exposing inbound webhook receivers.allow_guest=True on endpoints that change data or privileged state without extra gates (validation + rate limit + minimal surface).@frappe.whitelist() alone for authorization — treat login as authentication only; still enforce frappe.has_permission / frappe.only_for as needed (see Key Patterns above).frappe.log_error and a safe user-facing message.ignore_permissions=True without an explicit preceding role/security guard.@rate_limit on guest-visible endpoints to reduce abuse.From frappe-core-api: prefer token auth for integrations; store API secrets immediately when generated (shown once); session cookies expire (~3 days) — not for long-lived integrations; set Webhook secrets; use Jinja2 conditions on webhook conditions; outbound integration calls need timeouts and safe credential storage.
From frappe-syntax-whitelisted: HTTP parameters arrive as strings — coerce types and frappe.parse_json for JSON blobs; Desk callers should JSON.stringify complex frappe.call args; frappe.form_dict for dynamic param maps; [v15+] optional force_types and site hook require_type_annotated_api_methods enforce annotations; frappe.local.response["http_status_code"] for non-default RPC HTTP status where applicable.