About Experience Engineering Projects Infrastructure Blog Contact
ERPNext & Frappe 🧩 9-Part Course · Part 8: API, Deployment & Troubleshooting

ERPNext API, External Integration, Deployment & Troubleshooting

Published: Aug 30, 2026

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

MethodHow it worksWhen to use
API Key/SecretA token pair generated per User, sent as Authorization: token KEY:SECRETServer-to-server integrations β€” a backend service calling ERPNext on its own behalf, no human in the loop
Session/cookie authA logged-in session cookie, issued after a normal loginWhat the Desk UI itself uses in the browser β€” not typically what an external server-side integration should use
OAuth2A third-party app requests delegated access and acts on behalf of a specific ERPNext user, if OAuth2 is enabled on the siteA 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
↓REST API calls

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.

⚠️ Never connect a second application directly to ERPNext's MariaDB. A direct database connection bypasses the Frappe permission engine, field validation, and doc-event hooks entirely β€” writes made that way don't run 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 syncedDirectionTypical trigger
Customer syncExternal app β†’ ERPNextOn new signup in the external app
Product/Item syncERPNext β†’ External appScheduled or webhook-triggered β€” ERPNext is usually the source of truth for the catalog
Sales Order syncExternal app β†’ ERPNextOn checkout
Invoice/Payment status syncERPNext β†’ External appVia webhook on Sales Invoice/Payment Entry submit, so the external app can show "paid" without polling
⚠️ Pick one source of truth per entity type, upfront. Letting both systems write the same field independently β€” say, a Customer's address editable in both the external app and ERPNext β€” creates conflicting updates with no clean way to decide which one is correct. Deciding which system owns which entity is a design decision made before the integration is built, not something to patch in after sync conflicts start appearing.

πŸ”’ 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:

API Requestwith API Key/Secret
↓
Userthe account the key belongs to
↓
Role
↓
Role PermissionDocType-level: read/write/create/submit/cancel/delete
↓
User Permissionrestricts to specific records, e.g. one Company
↓
Document Permissionshare, workflow state, owner rules
↓
Allowed Action

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:

Scheduled bench backupdatabase + optional files
↓
Off-site / offsite storagenot just the same server
↓
Periodic restore testa backup nobody has ever restored is unverified
↓
Documented recovery runbookwho runs it, in what order, how long it takes

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
⚠️ Database backup and files backup are separate concerns. The database backup captures documents, but attachments (uploaded files, generated PDFs, images) live on disk under 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:

SymptomLikely causeFirst command/check
Permission error on a document a user swears they should accessRole Permission and User Permission are two separate layers β€” one can allow while the other blocksCheck 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 expectsRead the traceback for the exact version it's complaining about; compare against the app's stated requirements
Node version problem specificallyThe active Node version (via nvm or another version manager) doesn't match what the bench's package.json expectsnode -v vs. the bench's expected Node version; switch with your version manager
Python dependency conflict between two installed appsTwo apps declare incompatible pinned versions of the same Python package in the same bench environmentCheck env/ for the conflicting package; review both apps' requirements.txt
SSL/certificate renewal failureCertbot renewed the certificate but didn't actually reload Nginx, so the old cert is still being servedCheck Certbot's renewal logs, then manually reload/restart Nginx and re-check the served certificate
"Table doesn't exist" error after adding a Custom FieldThe schema change was defined but never migratedbench migrate
Background job silently never completingThe job is stuck or failing inside the worker process, not the schedulerCheck 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.

↑