π REST API, In Depth
Part 1's REST API Basics established the core, verified pattern: Frappe auto-generates a REST-style endpoint for every DocType at /api/resource/<DocType>, with the standard HTTP verbs mapping onto list/read, create, update, and delete:
GET /api/resource/Customer # list
GET /api/resource/Customer/CUST-00001 # read one
POST /api/resource/Customer # create
PUT /api/resource/Customer/CUST-00001 # update
DELETE /api/resource/Customer/CUST-00001 # delete
This article goes further into how that same list endpoint is actually used in practice: filtering, choosing which fields come back, and paging through large result sets. The exact query-parameter syntax below is illustrative β verify it against the API docs for your installed ERPNext/Frappe version before relying on it operationally.
# Filter a list β illustrative query-param syntax, verify per version
GET /api/resource/Customer?filters=[["customer_group","=","Retail"]]
# Choose which fields come back
GET /api/resource/Customer?fields=["name","customer_name"]
# Combine filter + field selection
GET /api/resource/Customer?filters=[["customer_group","=","Retail"]]&fields=["name","customer_name"]
# Pagination
GET /api/resource/Customer?limit_start=0&limit_page_length=20
GET /api/resource/Customer?limit_start=20&limit_page_length=20
Beyond the auto-generated per-DocType CRUD, a custom app (see Part 7's custom Frappe app development) can expose its own server-side functions as whitelisted methods β Python functions decorated to be callable over HTTP β reachable at a different URL shape:
# Calling a whitelisted custom method
POST /api/method/my_custom_app.api.approve_lab_report
# Python (requests)
import requests
headers = {"Authorization": "token API_KEY:API_SECRET"}
r = requests.post(
"https://site.example.com/api/method/my_custom_app.api.approve_lab_report",
headers=headers,
json={"lab_report": "LR-2026-00042"}
)
This is the mechanism that lets a custom app do more than expose its DocTypes as data β it can expose actual business operations (an approval, a calculation, a multi-document transaction) as a single callable endpoint, instead of forcing an external system to reconstruct that logic itself via multiple raw CRUD calls.
| Method | How it works | When to use |
|---|---|---|
| API Key/Secret | A token pair generated per User, sent as Authorization: token KEY:SECRET | Server-to-server integrations β a backend service calling ERPNext on its own behalf, no human in the loop |
| Session/cookie auth | A logged-in session cookie, issued after a normal login | What the Desk UI itself uses in the browser β not typically what an external server-side integration should use |
| OAuth2 | A third-party app requests delegated access and acts on behalf of a specific ERPNext user, if OAuth2 is enabled on the site | A third-party app acting on behalf of a specific user, rather than a trusted backend acting as itself |
ποΈ Connecting an External Application
Part 1's Internal vs External Connections diagram sketched the outside world touching ERPNext through the REST API. Here's the concrete shape that takes in a typical setup β a separate customer-facing application (e.g. Next.js frontend + Django backend) sitting in front of ERPNext rather than inside it:
Next.js Frontend
Django Backend
- owns its own users/auth
ERPNext REST API
ERPNext / Frappe Framework
- permissions, validation, hooks
MariaDB
The pattern this enables: the external Django app owns its own business logic and authentication for its own end users β signup, sessions, app-specific preferences β while treating ERPNext as the system of record for the business entities ERPNext already models well: Customers, Items, Sales Orders, Invoices, and Payments. The two systems stay in sync through API calls or webhooks, not a shared database.
validate(), don't trigger on_update, and aren't subject to Role/User/Document permissions. Always go through the REST API (or a whitelisted custom method) so every write still passes through the same checks a human user in the Desk would go through.
π Common Sync Patterns
In this two-system architecture, different entities tend to flow in different directions depending on which system actually owns that data:
| What's synced | Direction | Typical trigger |
|---|---|---|
| Customer sync | External app β ERPNext | On new signup in the external app |
| Product/Item sync | ERPNext β External app | Scheduled or webhook-triggered β ERPNext is usually the source of truth for the catalog |
| Sales Order sync | External app β ERPNext | On checkout |
| Invoice/Payment status sync | ERPNext β External app | Via webhook on Sales Invoice/Payment Entry submit, so the external app can show "paid" without polling |
π Securing the API in Production
Part 1's Security, Backup & Troubleshooting section covered the basics β HTTPS, scoped keys, rotation. In a real external integration, each of those needs to be treated as an operational discipline, not a one-time setup step:
- HTTPS only. Never send API keys, secrets, or payloads over plain HTTP β a key sent in cleartext is a key that has already leaked.
- Dedicated integration user. Create a separate User specifically for the integration and scope its API key to only the roles it actually needs β see Part 2's Role Permission Manager β rather than issuing a key against a System Manager account. If the key leaks, the blast radius is whatever that scoped role can do, not the whole site.
- Rotate and revoke through the User record. If a key is ever exposed β committed to a repo, logged accidentally, shared in a support ticket β regenerate it immediately from that User's API Access section; the old key stops working the moment a new one is generated.
- Rate-limit and monitor. Watch for unusual API call volume from a given key β a sudden spike is either a bug in the integration or a sign the key is being used somewhere it shouldn't be.
- Treat inbound webhook payloads as untrusted input. A webhook receiver on the external app's side should validate and sanitize whatever it receives before writing it back into ERPNext β never assume the payload is well-formed or that it actually originated from ERPNext without verifying it.
It's worth restating the layered permission chain from Part 1's Users, Roles & Permission Flow, because it applies identically to API traffic:
An API key does not bypass any of this β a request authenticated with an API key runs through the exact same Role/User/Document permission chain a human clicking through the Desk UI would go through. If the integration user's role can't submit a Sales Invoice, neither can a script calling that user's API key.
πΎ Backup & Recovery Strategy
Part 1 sketched backup as a single step in its security diagram. In production, it's really a four-stage discipline, and skipping any stage quietly turns "we have backups" into a false sense of safety:
For convenience, the relevant command from Part 1's Developer Command Cheat Sheet:
bench --site SITE backup # database backup (+ optional files, version-dependent)
# flags such as --with-files exist to include attachments in the same run β
# verify the exact flag names against your installed bench version
public/files/private/files and need their own backup step. A restore that only replays the database dump comes back missing every attachment anyone ever uploaded β both are required for a full recovery.
π§― Troubleshooting Reference, Expanded
Part 1's troubleshooting table covered the site-down basics: site doesn't open, MariaDB errors, Redis errors, scheduled jobs not firing, migration failure, build failure, and Nginx 502s. The symptoms below are ones that show up specifically once a site has external integrations, custom apps, and a real deployment pipeline running against it:
| Symptom | Likely cause | First command/check |
|---|---|---|
| Permission error on a document a user swears they should access | Role Permission and User Permission are two separate layers β one can allow while the other blocks | Check both the Role Permission Manager and User Permission list for that user, not just one |
App installation failure (bench get-app / install-app errors) | Usually a Python/Node version mismatch with what the app's requirements.txt/package.json expects | Read the traceback for the exact version it's complaining about; compare against the app's stated requirements |
| Node version problem specifically | The active Node version (via nvm or another version manager) doesn't match what the bench's package.json expects | node -v vs. the bench's expected Node version; switch with your version manager |
| Python dependency conflict between two installed apps | Two apps declare incompatible pinned versions of the same Python package in the same bench environment | Check env/ for the conflicting package; review both apps' requirements.txt |
| SSL/certificate renewal failure | Certbot renewed the certificate but didn't actually reload Nginx, so the old cert is still being served | Check Certbot's renewal logs, then manually reload/restart Nginx and re-check the served certificate |
| "Table doesn't exist" error after adding a Custom Field | The schema change was defined but never migrated | bench migrate |
| Background job silently never completing | The job is stuck or failing inside the worker process, not the scheduler | Check the worker's log specifically β not just the scheduler log β for the stuck/failed job |
β Production Readiness Checklist
Extending Part 1's Production Deployment Architecture into a concrete pre-launch checklist:
- Firewall configured β only ports 80, 443, and 22 open to the outside.
- HTTPS via Let's Encrypt/Certbot, with auto-renewal actually verified (not just configured) end to end, including the Nginx reload step.
- Supervisor managing Gunicorn, background workers, and the scheduler, with auto-restart on crash or reboot.
- Scheduled and tested backups, stored off-server β see Backup & Recovery Strategy above.
- Monitoring/alerting on the site being down or workers stalling, not just manual spot-checks.
- A staging environment that mirrors production, used to test every upgrade before it touches production β see Part 7's versioning & deployment practices for custom apps.
πΊοΈ What's Next
This article is Part 8 of the 9-part ERPNext series, going deep on the API integration and production-operations ground that Part 1 only sketched β see its REST API Basics and Production Deployment Architecture sections for where this all started. It also builds on Part 5's external connections coverage of how Accounting and Stock data reaches the outside world, and on Part 7's custom app development practices for exposing whitelisted methods and managing deployments.
Part 9 β the capstone β is next: the full Laboratory Management custom-module project, built end to end. Its Lab Report β Sales Invoice automation, and any patient/customer-facing portal exposure it adds, are built using exactly the API design, security hardening, and deployment practices covered in this article β modules, database design, customization, and custom app development from Parts 3 through 7 all converge there into one working project.