agentleFS
lubusINlubusIN/frappe-skills/api-development/SKILL.md

api-development

Skill68 starsChanged 8 days agoMITView on GitHub

Build 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.

10 other projects use a version of this.

Adopted by

Most recently taken up by

Used with

Projects with it also use git-workflow.

Other files people use alongside this one.

Files found in the same public projects as a version of this one, more often than they turn up anywhere else. Each project counts once, large collections count for less, and this file's own repository is left out.
  • Microsoft AzureSkill

    git-workflow

    Squad branching model: dev-first workflow with insiders preview channel

    Also in 4 projects that use this

The file

We split this skill into the rules it gives an agent

9 rules. 0 are common, 4 are distinctive, and 5 are very specific to this project.

  • 0

    Common

    Most of the 9 edited versions we read keep these.

  • 4

    Distinctive

    This author's own choices. Worth a look.

  • 5

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Distinctive:

    Create whitelisted methods in your app:

    line 61
  2. Distinctive:

    Method not found: Check module path in URL matches Python path

    line 203
  3. Very specific:

    Permission denied: Verify @frappe.whitelist() decorator and user permissions

    line 204
  4. Distinctive:

    CSRF error: Use proper auth headers for API calls

    line 205
  5. Very specific:

    500 error: Check error logs: bench --site <site> show-log

    line 206
  6. Distinctive:

    Always validate input: Never trust client data; validate type, length, and format server-side

    line 223
  7. Very specific:

    Use permission callbacks: Check frappe.has_permission() explicitly in whitelisted methods

    line 224
  8. Very specific:

    Sanitize user input: Use frappe.db.escape() for SQL, avoid eval() and dynamic code execution

    line 225
  9. Very specific:

    Return structured errors: Use frappe.throw() with proper HTTP status codes

    line 227

“Common” is measured: kept by at least 6 in 10 of the 9 edited versions of this file we could read. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

Frappe API Development

Build secure, well-designed APIs using Frappe's REST and RPC patterns.

When to use

  • Creating custom RPC endpoints (@frappe.whitelist)
  • Building REST API integrations
  • Implementing webhooks for external systems
  • Setting up API authentication (token, OAuth)
  • Exposing business logic to frontends

Inputs required

  • API purpose (CRUD, action, integration)
  • Authentication requirements (public, user, API key)
  • Permission requirements per endpoint
  • Request/response format expectations

Procedure

0) Choose API pattern

NeedPattern
DocType CRUDUse built-in REST API
Custom actionRPC with @frappe.whitelist
External callbackWebhook DocType
Batch operationsBackground job + status endpoint

1) Built-in REST API (DocType CRUD)

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"]]

2) Custom RPC endpoints

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"}

3) Implement authentication

API Key + Secret (recommended for integrations):

# Header format
Authorization: token api_key:api_secret

Bearer Token:

Authorization: Bearer <token>

Session (for logged-in users): Automatic via cookies.

4) Permission checks

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
    ...

5) Input validation

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

6) Response format

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": "..."}

7) Background jobs for long operations

@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()}

Verification

  • Endpoint responds correctly to valid requests
  • Permission errors returned for unauthorized access
  • Input validation rejects invalid data
  • Error responses are structured and helpful
  • Run: bench --site <site> console → test endpoint manually

Failure modes / debugging

  • Method not found: Check module path in URL matches Python path
  • Permission denied: Verify @frappe.whitelist() decorator and user permissions
  • CSRF error: Use proper auth headers for API calls
  • 500 error: Check error logs: bench --site <site> show-log

Escalation

  • For OAuth integration, see references/oauth.md
  • For webhook patterns, see references/webhooks.md
  • For rate limiting, see references/rate-limiting.md

References

  • references/rest-api.md - REST API details
  • references/authentication.md - Auth patterns
  • references/permissions.md - Permission system
  • references/webhooks.md - Outbound webhooks

Guardrails

  • Always validate input: Never trust client data; validate type, length, and format server-side
  • Use permission callbacks: Check frappe.has_permission() explicitly in whitelisted methods
  • Sanitize user input: Use frappe.db.escape() for SQL, avoid eval() and dynamic code execution
  • Handle rate limiting: Implement rate limits for public APIs to prevent abuse
  • Return structured errors: Use frappe.throw() with proper HTTP status codes

Common Mistakes

MistakeWhy It FailsFix
Missing @frappe.whitelist()Method returns "Method not found" errorAdd decorator to expose method via API
Using GET for mutationsViolates REST conventions, CSRF issuesUse POST/PUT/DELETE for data changes
Not handling errors500 errors expose stack tracesWrap in try/except, use frappe.throw()
Exposing sensitive dataSecurity breachFilter response fields, check permissions
Missing allow_guest=TruePublic endpoints return 403Add @frappe.whitelist(allow_guest=True) for unauthenticated access
SQL injection in queriesDatabase compromiseUse Query Builder or frappe.db.escape()

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.