npx skills add imbilawork/2nth-skills@erpnext-furniture
OVERVIEW
ERPNext is an open-source ERP built on the Frappe framework. It's widely used in manufacturing, especially for make-to-order and make-to-stock operations. This skill is tailored for furniture manufacturing — covering BOMs, production routing, timber/hardware inventory, and quality inspection.
ERPNext exposes a comprehensive REST API for every doctype, plus an RPC API for server-side methods. This skill gives AI agents the knowledge to query, automate, and report on a furniture factory — without hallucinating endpoints or field names.
ARCHITECTURE
The recommended pattern uses a Cloudflare Worker as a proxy — this avoids CORS, secures API keys at the edge, and enables AI analysis via Workers AI.
Proxy + AI
REST / RPC API
Base URL
GET https://<site>/api/resource/<DocType>
POST https://<site>/api/method/<dotted.path>
Authentication
# API Key + Secret (recommended)
Authorization: token api_key:api_secret
# Or Basic Auth
Authorization: Basic base64(user:password)
Never expose API keys in client-side code. Use a Cloudflare Worker proxy with secrets stored via wrangler secret put.
CAPABILITIES
What your agent can do with this skill installed:
- Manage BOMs — create, explode, and cost Bills of Materials for furniture
- Schedule production — work orders, job cards, workstation capacity
- Track inventory — stock levels, reorder alerts, material transfers
- Process sales — orders, deliveries, invoices, customer analytics
- Handle procurement — purchase orders, supplier performance, cost tracking
- Quality control — inspections, readings, pass/fail automation
- Generate reports — revenue, production output, material usage, margins
- Natural language queries — translate questions into ERPNext API calls
- Deploy to edge — Cloudflare Workers proxy with AI analysis
KEY DOCTYPES
ERPNext uses "doctypes" — each is a data model with its own REST endpoint.
| Doctype | Endpoint | Furniture Context |
|---|---|---|
| Item | /api/resource/Item | Finished furniture, timber, hardware, fabric |
| BOM | /api/resource/BOM | Recipe: 4 legs + 1 top + 8 screws = Table |
| Work Order | /api/resource/Work Order | "Make 30 Dining Tables" |
| Job Card | /api/resource/Job Card | CNC cutting, assembly, finishing steps |
| Workstation | /api/resource/Workstation | CNC Router, Spray Booth, Assembly Line |
| Stock Entry | /api/resource/Stock Entry | Material issue, manufacture, transfer |
| Quality Inspection | /api/resource/Quality Inspection | Surface finish, dimensions, joint strength |
| Sales Order | /api/resource/Sales Order | Retailer and custom orders |
| Purchase Order | /api/resource/Purchase Order | Timber, hardware, finish supplies |
Item Groups (furniture setup)
MANUFACTURING FLOW
from BOM
per operation
manufacture
Operations Routing (typical furniture)
| # | Operation | Workstation | Time |
|---|---|---|---|
| 1 | CNC Cutting | CNC Router | 15-45 min |
| 2 | Machining | CNC Router / Mortiser | 10-30 min |
| 3 | Joining | Assembly Bench | 20-60 min |
| 4 | Sanding | Sanding Station | 15-30 min |
| 5 | Finishing | Spray Booth / Oil Station | 20-45 min |
| 6 | Upholstery | Upholstery Bench | 30-60 min |
| 7 | Final Assembly | Assembly Line | 15-45 min |
| 8 | QC | Inspection Station | 10-20 min |
Example BOM — Dining Table
{
"item": "NOHA-DINING-TABLE-8",
"quantity": 1,
"items": [
{"item_code": "RM-OAK-25MM", "qty": 24, "uom": "Board Foot"},
{"item_code": "RM-OAK-VENEER", "qty": 2.4, "uom": "Square Meter"},
{"item_code": "HW-BOLT-M8X60", "qty": 16, "uom": "Nos"},
{"item_code": "HW-BRACKET-L", "qty": 8, "uom": "Nos"},
{"item_code": "FN-DANISH-OIL", "qty": 0.8, "uom": "Litre"},
{"item_code": "FN-SANDPAPER-180","qty": 4, "uom": "Sheet"}
]
}
API PATTERNS
List Records
GET /api/resource/Item
?filters=[["item_group","=","Finished Goods"]]
&fields=["name","item_name","standard_rate","stock_uom"]
&limit_page_length=50
&order_by=item_name asc
Active Work Orders
GET /api/resource/Work Order
?filters=[["status","in",["Not Started","In Process"]],["docstatus","=",1]]
&fields=["name","production_item","qty","produced_qty","status","planned_start_date"]
&order_by=planned_start_date asc
Explode BOM (get all materials for a batch)
POST /api/method/erpnext.manufacturing.doctype.bom.bom.get_bom_items
{"bom":"BOM-NOHA-DINING-TABLE-001","qty":30,"fetch_exploded":1}
Stock Balance
POST /api/method/erpnext.stock.utils.get_stock_balance
{"item_code":"RM-OAK-25MM","warehouse":"Raw Materials Store"}
Filter Operators
| Operator | Meaning | Example |
|---|---|---|
= | Equals | ["status","=","Open"] |
!= | Not equals | ["status","!=","Cancelled"] |
> / >= | Greater than | ["qty",">",0] |
like | Wildcard | ["item_name","like","%Oak%"] |
in | In list | ["status","in",["Open","In Process"]] |
between | Date range | ["posting_date","between",["2026-01-01","2026-03-31"]] |
REPORTING PATTERNS
ERPNext supports server-side aggregation via frappe.client.get_list with group_by.
Revenue by Customer
POST /api/method/frappe.client.get_list
{
"doctype": "Sales Invoice",
"filters": [["docstatus","=",1],["posting_date",">=","2026-01-01"]],
"fields": ["customer","sum(grand_total) as revenue"],
"group_by": "customer",
"order_by": "revenue desc",
"limit_page_length": 20
}
Production Output by Product
POST /api/method/frappe.client.get_list
{
"doctype": "Work Order",
"filters": [["status","=","Completed"],["actual_end_date",">=","2026-03-01"]],
"fields": ["production_item","sum(produced_qty) as total_produced"],
"group_by": "production_item",
"order_by": "total_produced desc"
}
Material Usage This Month
GET /api/resource/Stock Ledger Entry
?filters=[["voucher_type","=","Stock Entry"],["posting_date",">=","2026-03-01"],["actual_qty","<",0]]
&fields=["item_code","sum(actual_qty) as total_issued"]
&group_by=item_code
&order_by=total_issued asc
AI INTEGRATION
Natural language in, operational intelligence out.
API call
ERPNext
results
Cloudflare Worker Proxy
async function proxyERPNext(request, env) {
const url = new URL(request.url);
const erpPath = url.pathname.replace('/api/erp/', '/api/');
const erpUrl = `${env.ERPNEXT_URL}${erpPath}${url.search}`;
return fetch(erpUrl, {
method: request.method,
headers: {
'Content-Type': 'application/json',
'Authorization': `token ${env.ERPNEXT_API_KEY}:${env.ERPNEXT_API_SECRET}`,
},
body: request.method !== 'GET' ? await request.text() : undefined,
});
}
System Prompt for Query Generation
You are an ERPNext API expert for a furniture manufacturer.
Generate the API call for the user's question.
API: GET /api/resource/<DocType>?filters=...&fields=...
RPC: POST /api/method/<dotted.path>
Key doctypes: Item, BOM, Work Order, Job Card, Stock Entry,
Sales Order, Purchase Order, Quality Inspection, Bin.
Filters: JSON arrays [field, operator, value].
Always include docstatus=1 for submitted docs.
Respond with HTTP method, path, and body only.
INSTALL
Works with Claude Code, Cursor, Windsurf, Cline, and 30+ other AI agents:
npx skills add imbilawork/2nth-skills@erpnext-furniture
Or install globally with no prompts:
npx skills add imbilawork/2nth-skills@erpnext-furniture -g -y
The skill loads automatically when your agent works on ERPNext or manufacturing tasks.
GOTCHAS
| Issue | Detail |
|---|---|
| Docstatus | 0=Draft, 1=Submitted, 2=Cancelled. Most queries need ["docstatus","=",1] |
| Pagination | Default limit_page_length=20. Set explicitly or use 0 for all (careful) |
| Child tables | BOM items, SO items are child tables — fetch via parent or fields=["items.item_code"] |
| Rate limits | Frappe defaults to 5 req/sec for API keys |
| Naming series | Use name (ID) not item_name for lookups |
| BOM versioning | Multiple BOMs per item — filter is_active=1 and is_default=1 |
| Stock Entry types | "Material Issue", "Material Transfer for Manufacture", "Manufacture" |
| CORS | Browser requests may be blocked — use a server-side proxy |