$npx -y skills add lubusIN/frappe-skills --skill frappe-api-developmentBuild REST and RPC APIs in Frappe including whitelisted methods, authentication, and permission handling. Use when creating custom endpoints, integrating with external systems, or exposing business logic via API.
| 1 | # Frappe API Development |
| 2 | |
| 3 | Build secure, well-designed APIs using Frappe's REST and RPC patterns. |
| 4 | |
| 5 | ## When to use |
| 6 | |
| 7 | - Creating custom RPC endpoints (`@frappe.whitelist`) |
| 8 | - Building REST API integrations |
| 9 | - Implementing webhooks for external systems |
| 10 | - Setting up API authentication (token, OAuth) |
| 11 | - Exposing business logic to frontends |
| 12 | |
| 13 | ## Inputs required |
| 14 | |
| 15 | - API purpose (CRUD, action, integration) |
| 16 | - Authentication requirements (public, user, API key) |
| 17 | - Permission requirements per endpoint |
| 18 | - Request/response format expectations |
| 19 | |
| 20 | ## Procedure |
| 21 | |
| 22 | ### 0) Choose API pattern |
| 23 | |
| 24 | | Need | Pattern | |
| 25 | |------|---------| |
| 26 | | DocType CRUD | Use built-in REST API | |
| 27 | | Custom action | RPC with `@frappe.whitelist` | |
| 28 | | External callback | Webhook DocType | |
| 29 | | Batch operations | Background job + status endpoint | |
| 30 | |
| 31 | ### 1) Built-in REST API (DocType CRUD) |
| 32 | |
| 33 | Frappe provides automatic REST endpoints for all DocTypes: |
| 34 | |
| 35 | ```bash |
| 36 | # Create |
| 37 | POST /api/resource/Customer |
| 38 | {"customer_name": "Acme Corp"} |
| 39 | |
| 40 | # Read |
| 41 | GET /api/resource/Customer/CUST-001 |
| 42 | |
| 43 | # Update |
| 44 | PUT /api/resource/Customer/CUST-001 |
| 45 | {"customer_name": "Acme Corporation"} |
| 46 | |
| 47 | # Delete |
| 48 | DELETE /api/resource/Customer/CUST-001 |
| 49 | |
| 50 | # List with filters |
| 51 | GET /api/resource/Customer?filters=[["status","=","Active"]] |
| 52 | ``` |
| 53 | |
| 54 | ### 2) Custom RPC endpoints |
| 55 | |
| 56 | Create whitelisted methods in your app: |
| 57 | |
| 58 | ```python |
| 59 | # my_app/api.py |
| 60 | import frappe |
| 61 | |
| 62 | @frappe.whitelist() |
| 63 | def process_order(order_id, action): |
| 64 | """Process an order with the given action.""" |
| 65 | # Always verify permissions |
| 66 | doc = frappe.get_doc("Sales Order", order_id) |
| 67 | if not frappe.has_permission("Sales Order", "write", doc): |
| 68 | frappe.throw("Not permitted", frappe.PermissionError) |
| 69 | |
| 70 | # Business logic |
| 71 | if action == "approve": |
| 72 | doc.status = "Approved" |
| 73 | doc.save() |
| 74 | |
| 75 | return {"status": "success", "order": doc.name} |
| 76 | |
| 77 | @frappe.whitelist(allow_guest=True) |
| 78 | def public_endpoint(): |
| 79 | """Public endpoint - no auth required.""" |
| 80 | return {"message": "Hello, World!"} |
| 81 | ``` |
| 82 | |
| 83 | Call via: |
| 84 | ```bash |
| 85 | POST /api/method/my_app.api.process_order |
| 86 | {"order_id": "SO-001", "action": "approve"} |
| 87 | ``` |
| 88 | |
| 89 | ### 3) Implement authentication |
| 90 | |
| 91 | **API Key + Secret (recommended for integrations):** |
| 92 | ```bash |
| 93 | # Header format |
| 94 | Authorization: token api_key:api_secret |
| 95 | ``` |
| 96 | |
| 97 | **Bearer Token:** |
| 98 | ```bash |
| 99 | Authorization: Bearer <token> |
| 100 | ``` |
| 101 | |
| 102 | **Session (for logged-in users):** |
| 103 | Automatic via cookies. |
| 104 | |
| 105 | ### 4) Permission checks |
| 106 | |
| 107 | **ALWAYS check permissions in RPC methods:** |
| 108 | |
| 109 | ```python |
| 110 | @frappe.whitelist() |
| 111 | def sensitive_action(docname): |
| 112 | doc = frappe.get_doc("My DocType", docname) |
| 113 | |
| 114 | # Check document-level permission |
| 115 | if not frappe.has_permission("My DocType", "write", doc): |
| 116 | frappe.throw("Not permitted", frappe.PermissionError) |
| 117 | |
| 118 | # Check role-based permission |
| 119 | if "Manager" not in frappe.get_roles(): |
| 120 | frappe.throw("Manager role required") |
| 121 | |
| 122 | # Proceed with action |
| 123 | ... |
| 124 | ``` |
| 125 | |
| 126 | ### 5) Input validation |
| 127 | |
| 128 | ```python |
| 129 | @frappe.whitelist() |
| 130 | def create_item(name, qty, price): |
| 131 | # Validate required fields |
| 132 | if not name: |
| 133 | frappe.throw("Name is required") |
| 134 | |
| 135 | # Validate types |
| 136 | qty = frappe.utils.cint(qty) |
| 137 | price = frappe.utils.flt(price) |
| 138 | |
| 139 | # Validate ranges |
| 140 | if qty <= 0: |
| 141 | frappe.throw("Quantity must be positive") |
| 142 | |
| 143 | # Proceed |
| 144 | ... |
| 145 | ``` |
| 146 | |
| 147 | ### 6) Response format |
| 148 | |
| 149 | **Success response:** |
| 150 | ```python |
| 151 | return { |
| 152 | "status": "success", |
| 153 | "data": {...} |
| 154 | } |
| 155 | ``` |
| 156 | |
| 157 | **Error handling:** |
| 158 | ```python |
| 159 | # User-facing error |
| 160 | frappe.throw("Validation failed", title="Error") |
| 161 | |
| 162 | # Permission error |
| 163 | frappe.throw("Not allowed", frappe.PermissionError) |
| 164 | |
| 165 | # Standard exceptions become {"exc_type": "...", "exc": "..."} |
| 166 | ``` |
| 167 | |
| 168 | ### 7) Background jobs for long operations |
| 169 | |
| 170 | ```python |
| 171 | @frappe.whitelist() |
| 172 | def start_export(filters): |
| 173 | job = frappe.enqueue( |
| 174 | "my_app.jobs.run_export", |
| 175 | filters=filters, |
| 176 | queue="long", |
| 177 | timeout=600 |
| 178 | ) |
| 179 | return {"job_id": job.id} |
| 180 | |
| 181 | @frappe.whitelist() |
| 182 | def check_job_status(job_id): |
| 183 | from frappe.utils.background_jobs import get_job |
| 184 | job = get_job(job_id) |
| 185 | return {"status": job.get_status()} |
| 186 | ``` |
| 187 | |
| 188 | ## Verification |
| 189 | |
| 190 | - [ ] Endpoint responds correctly to valid requests |
| 191 | - [ ] Permission errors returned for unauthorized access |
| 192 | - [ ] Input validation rejects invalid data |
| 193 | - [ ] Error responses are structured and helpful |
| 194 | - [ ] Run: `bench --site <site> console` → test endpoint manually |
| 195 | |
| 196 | ## Failure modes / debugging |
| 197 | |
| 198 | - **Method not found**: Check module path in URL matches Python path |
| 199 | - **Permission denied**: Verify `@frappe.whitelist()` decorator and user permissions |
| 200 | - **CSRF error**: Use proper auth headers for API calls |
| 201 | - **500 error**: Check error logs: `bench --site <site> show-log` |
| 202 | |
| 203 | ## Escalation |
| 204 | |
| 205 | - For OAuth integration, see [references/oauth.md](refer |