frappe-api-development — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited frappe-api-development (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
Build secure, well-designed APIs using Frappe's REST and RPC patterns.
@frappe.whitelist)| Need | Pattern |
|---|---|
| DocType CRUD | Use built-in REST API |
| Custom action | RPC with @frappe.whitelist |
| External callback | Webhook DocType |
| Batch operations | Background job + status endpoint |
Frappe provides automatic REST endpoints for all DocTypes:
# Create
POST /api/resource/Customer
{"customer_name": "Acme Corp"}
# Read
GET /api/resource/Customer/CUST-001
# Update
PUT /api/resource/Customer/CUST-001
{"customer_name": "Acme Corporation"}
# Delete
DELETE /api/resource/Customer/CUST-001
# List with filters
GET /api/resource/Customer?filters=[["status","=","Active"]]Create whitelisted methods in your app:
# my_app/api.py
import frappe
@frappe.whitelist()
def process_order(order_id, action):
"""Process an order with the given action."""
# Always verify permissions
doc = frappe.get_doc("Sales Order", order_id)
if not frappe.has_permission("Sales Order", "write", doc):
frappe.throw("Not permitted", frappe.PermissionError)
# Business logic
if action == "approve":
doc.status = "Approved"
doc.save()
return {"status": "success", "order": doc.name}
@frappe.whitelist(allow_guest=True)
def public_endpoint():
"""Public endpoint - no auth required."""
return {"message": "Hello, World!"}Call via:
POST /api/method/my_app.api.process_order
{"order_id": "SO-001", "action": "approve"}API Key + Secret (recommended for integrations):
# Header format
Authorization: token api_key:api_secretBearer Token:
Authorization: Bearer <token>Session (for logged-in users): Automatic via cookies.
ALWAYS check permissions in RPC methods:
@frappe.whitelist()
def sensitive_action(docname):
doc = frappe.get_doc("My DocType", docname)
# Check document-level permission
if not frappe.has_permission("My DocType", "write", doc):
frappe.throw("Not permitted", frappe.PermissionError)
# Check role-based permission
if "Manager" not in frappe.get_roles():
frappe.throw("Manager role required")
# Proceed with action
...@frappe.whitelist()
def create_item(name, qty, price):
# Validate required fields
if not name:
frappe.throw("Name is required")
# Validate types
qty = frappe.utils.cint(qty)
price = frappe.utils.flt(price)
# Validate ranges
if qty <= 0:
frappe.throw("Quantity must be positive")
# Proceed
...Success response:
return {
"status": "success",
"data": {...}
}Error handling:
# User-facing error
frappe.throw("Validation failed", title="Error")
# Permission error
frappe.throw("Not allowed", frappe.PermissionError)
# Standard exceptions become {"exc_type": "...", "exc": "..."}@frappe.whitelist()
def start_export(filters):
job = frappe.enqueue(
"my_app.jobs.run_export",
filters=filters,
queue="long",
timeout=600
)
return {"job_id": job.id}
@frappe.whitelist()
def check_job_status(job_id):
from frappe.utils.background_jobs import get_job
job = get_job(job_id)
return {"status": job.get_status()}bench --site <site> console → test endpoint manually@frappe.whitelist() decorator and user permissionsbench --site <site> show-logfrappe.has_permission() explicitly in whitelisted methodsfrappe.db.escape() for SQL, avoid eval() and dynamic code executionfrappe.throw() with proper HTTP status codes| Mistake | Why It Fails | Fix |
|---|---|---|
Missing @frappe.whitelist() | Method returns "Method not found" error | Add decorator to expose method via API |
| Using GET for mutations | Violates REST conventions, CSRF issues | Use POST/PUT/DELETE for data changes |
| Not handling errors | 500 errors expose stack traces | Wrap in try/except, use frappe.throw() |
| Exposing sensitive data | Security breach | Filter response fields, check permissions |
Missing allow_guest=True | Public endpoints return 403 | Add @frappe.whitelist(allow_guest=True) for unauthenticated access |
| SQL injection in queries | Database compromise | Use Query Builder or frappe.db.escape() |
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.