Frappe Core Api logo

Frappe Core Api

Organization
Impertio-Studio
frappe-core-api

Use when building ERPNext/Frappe API integrations (v14/v15/v16) including REST API, RPC API, authentication, webhooks, and rate limiting. Covers external API calls, endpoint design, token/OAuth2/session authentication. Keywords: API integration, REST endpoint, webhook, token authentication,, how to connect, external API, send data to another system, API not working, 401 error. OAuth, frappe.call, external connection, rate limiting.

Overview

PublisherImpertio-Studio
RepositoryFrappe_Claude_Skill_Package
Skill namefrappe-core-api
Stars
180
Forks
53
Bundled files
6
LicenseMIT
Links
  • Markdown instructions

    A SKILL.md file the model loads on demand, so it only costs tokens when a request actually matches.

  • Works with any LLM

    AI skills are plain Markdown, not provider-specific code, so this works with GPT, Claude, Gemini, Grok, or a local model.

  • 6 bundled files

    Scripts, templates, and references the model can read while it works. Files are read-only and never executed.

  • Open source

    Published by Impertio-Studio on GitHub. Read the source before you install it.

Installation

Install the Frappe Core Api AI skill in TypingMind to use it with any LLM, or drop it into another agent that reads SKILL.md.

1

Install in TypingMind

TypingMind installs a skill straight from its GitHub folder — it reads SKILL.md, bundles the resource files, and stores the result locally.

  1. Open the app and go to Plugins → Skills.
  2. Choose "Install from GitHub".
  3. Paste the skill folder URL below and confirm.
  4. Enable the skill in any chat where you want it available.
Plugins → Skills → Add skill → From GitHub URL, then paste the folder URL and press Continue.
2

Install in another agent

Any agent that reads the Agent Skills format can use this skill — copy the folder into that agent's skills directory.

Claude Code — .claude/skills
git clone --depth 1 https://github.com/Impertio-Studio/Frappe_Claude_Skill_Package.git /tmp/Frappe_Claude_Skill_Package
mkdir -p .claude/skills
cp -r /tmp/Frappe_Claude_Skill_Package/skills/source/core/frappe-core-api .claude/skills/frappe-core-api
Restart Claude Code after copying so it picks up the new skill.

Use it in TypingMind

Enable Frappe Core Api in any TypingMind chat and the model takes it from there. Its name and description sit in the system prompt, and the moment a request matches, the model loads the full instructions itself — you never invoke it by hand, and it costs no tokens until it is actually used.

The model loads Frappe Core Api on its own as soon as a request matches it.

Works with any AI model

AI skills are plain Markdown instructions rather than provider-specific code, so Frappe Core Api is not tied to the model it was written for. Install it once in TypingMind and use it with GPT-5, Claude, Gemini, Grok, DeepSeek, Mistral, Llama, or a local model you run yourself — all on your own API keys.

  • Loaded only when it is needed

    The system prompt carries just the name and description. The instructions are fetched on the first matching request, so an idle skill costs nothing.

  • Switch models mid-chat

    Because the skill is instructions rather than code, changing model does not break it — the next model reads the same SKILL.md.

Skill instructions

This is the SKILL.md content the model loads. Read it before installing — a skill is instructions your model will follow.

Frappe API Patterns

Deterministic patterns for REST, RPC, and webhook integrations with Frappe.


Decision Tree

What do you need?
├── CRUD on documents (external client)
│   ├── v14: REST /api/resource/{doctype}
│   └── v15+: REST /api/v2/document/{doctype} (new) or /api/resource/ (still works)
├── Call custom server logic (external client)
│   └── RPC: POST /api/method/{dotted.path.to.function}
├── Notify external systems on document events
│   └── Webhooks (configured in UI or via DocType)
├── Client-side calls (JavaScript in Frappe desk)
│   ├── frappe.xcall() — async/await (RECOMMENDED)
│   └── frappe.call() — callback/promise pattern
└── Authentication method?
    ├── Server-to-server integration → Token Auth (RECOMMENDED)
    ├── Third-party app / mobile → OAuth 2.0
    ├── Browser session (short-lived) → Session/Cookie Auth
    └── Quick scripting / testing → Token Auth

Authentication Methods

Token Auth (RECOMMENDED for integrations)

python
headers = {
    'Authorization': 'token api_key:api_secret',
    'Accept': 'application/json',
    'Content-Type': 'application/json'
}

Generate keys: User > Settings > API Access > Generate Keys. ALWAYS store API secret immediately — it is shown only once.

Basic Auth (alternative token format)

python
import base64
credentials = base64.b64encode(b'api_key:api_secret').decode()
headers = {'Authorization': f'Basic {credentials}'}

OAuth 2.0 (third-party apps)

# Step 1: Authorization redirect
GET /api/method/frappe.integrations.oauth2.authorize
    ?client_id={id}&response_type=code&scope=openid all
    &redirect_uri={uri}&state={random}

# Step 2: Exchange code for token
POST /api/method/frappe.integrations.oauth2.get_token
    grant_type=authorization_code&code={code}
    &redirect_uri={uri}&client_id={id}

# Step 3: Use bearer token
Authorization: Bearer {access_token}

# Refresh token
POST /api/method/frappe.integrations.oauth2.get_token
    grant_type=refresh_token&refresh_token={token}&client_id={id}

Session/Cookie Auth

python
session = requests.Session()
session.post(url + '/api/method/login', json={'usr': 'email', 'pwd': 'pass'})
# Subsequent requests use session cookie automatically

Session cookies expire after ~3 days. NEVER use for long-running integrations.


REST API: Resource CRUD

Endpoints

OperationMethodv14 Endpointv15+ v2 Endpoint
ListGET/api/resource/{doctype}/api/v2/document/{doctype}
CreatePOST/api/resource/{doctype}/api/v2/document/{doctype}
ReadGET/api/resource/{doctype}/{name}/api/v2/document/{doctype}/{name}
UpdatePUT/api/resource/{doctype}/{name}PATCH /api/v2/document/{doctype}/{name}
DeleteDELETE/api/resource/{doctype}/{name}DELETE /api/v2/document/{doctype}/{name}
CopyGET /api/v2/document/{doctype}/{name}/copy [v15+]
Doc MethodPOST /api/v2/document/{doctype}/{name}/method/{method} [v15+]

ALWAYS include Accept: application/json header — without it, Frappe MAY return HTML.

List Parameters

ParameterTypeDescriptionDefault
fieldsJSON arrayFields to return["name"]
filtersJSON arrayAND conditionsnone
or_filtersJSON arrayOR conditionsnone
order_bystringSort expressionmodified desc
limit_startintPagination offset0
limit_page_lengthintPage size20
limitintAlias for limit_page_length [v15+]
debugboolShow SQL in responsefalse

Filter Operators

python
filters = [["status", "=", "Open"]]
filters = [["amount", ">", 1000]]
filters = [["status", "in", ["Open", "Pending"]]]
filters = [["date", "between", ["2024-01-01", "2024-12-31"]]]
filters = [["reference", "is", "set"]]       # NOT NULL
filters = [["reference", "is", "not set"]]   # IS NULL
filters = [["name", "like", "%INV%"]]
filters = [["status", "not in", ["Cancelled"]]]

Full operator list: =, !=, >, <, >=, <=, like, not like, in, not in, is, between.

Pagination Pattern

python
import json, requests

def get_all_records(doctype, headers, base_url, page_size=100):
    all_data, offset = [], 0
    while True:
        params = {
            'fields': json.dumps(["name", "modified"]),
            'limit_start': offset,
            'limit_page_length': page_size
        }
        resp = requests.get(f'{base_url}/api/resource/{doctype}',
                            params=params, headers=headers)
        data = resp.json().get('data', [])
        if not data:
            break
        all_data.extend(data)
        if len(data) < page_size:
            break
        offset += page_size
    return all_data

Create with Child Table

python
requests.post(f'{base_url}/api/resource/Sales Order', json={
    "customer": "CUST-001",
    "items": [
        {"item_code": "ITEM-001", "qty": 5, "rate": 100},
        {"item_code": "ITEM-002", "qty": 2, "rate": 250}
    ]
}, headers=headers)

Update (Partial)

python
# Only specified fields are changed
requests.put(f'{base_url}/api/resource/Customer/CUST-001',
             json={"customer_group": "Premium"}, headers=headers)

File Upload

python
requests.post(f'{base_url}/api/method/upload_file',
    files={'file': ('doc.pdf', open('doc.pdf', 'rb'), 'application/pdf')},
    data={'doctype': 'Customer', 'docname': 'CUST-001', 'is_private': 1},
    headers={'Authorization': 'token api_key:api_secret'})
# NOTE: Do NOT set Content-Type header — requests sets multipart boundary automatically

RPC API: Custom Methods

Server-Side Endpoint

python
@frappe.whitelist()
def get_balance(customer):
    """GET /api/method/myapp.api.get_balance?customer=CUST-001"""
    return frappe.db.get_value("Customer", customer, "outstanding_amount")

@frappe.whitelist(methods=["POST"])
def create_payment(customer, amount):
    """POST /api/method/myapp.api.create_payment"""
    if not frappe.has_permission("Payment Entry", "create"):
        frappe.throw(_("Not permitted"), frappe.PermissionError)
    pe = frappe.new_doc("Payment Entry")
    pe.party_type = "Customer"
    pe.party = customer
    pe.paid_amount = float(amount)
    pe.insert()
    return pe.name

@frappe.whitelist(allow_guest=True)
def public_status():
    """No authentication required."""
    return {"status": "ok"}

Decorator Options

OptionEffectVersion
allow_guest=TrueNo authentication neededAll
methods=["POST"]Restrict HTTP methods[v14+]
xss_safe=TrueSkip XSS escaping on responseAll

Response Structure

json
// RPC success
{"message": "return_value"}

// REST success
{"data": {...}}

// Error
{"exc_type": "ValidationError", "_server_messages": "[{\"message\": \"...\"}]"}

Client-Side Calls (JavaScript)

javascript
// RECOMMENDED: async/await with frappe.xcall
const result = await frappe.xcall('myapp.api.get_balance', {
    customer: 'CUST-001'
});

// Alternative: frappe.call with promise
frappe.call({
    method: 'myapp.api.get_balance',
    args: {customer: 'CUST-001'},
    freeze: true,
    freeze_message: __('Loading...')
}).then(r => console.log(r.message));

// Document method (frm.call)
frm.call('get_linked_doc', {throw_if_missing: true})
    .then(r => console.log(r.message));

Standard frappe.client Methods

MethodEndpointPurpose
frappe.client.get_valuePOSTGet single field value
frappe.client.get_listPOSTList with filters
frappe.client.getPOSTGet full document
frappe.client.insertPOSTCreate document
frappe.client.savePOSTUpdate document
frappe.client.deletePOSTDelete document
frappe.client.submitPOSTSubmit document
frappe.client.cancelPOSTCancel document
frappe.client.get_countPOSTCount documents

Webhooks

Configure via Webhook DocType in the UI. Events:

EventTrigger
after_insertNew document created
on_updateEvery save
on_submitAfter submit (docstatus=1)
on_cancelAfter cancel (docstatus=2)
on_trashBefore delete
on_update_after_submitAfter amendment
on_changeOn every change

Security: ALWAYS set a Webhook Secret. Frappe adds X-Frappe-Webhook-Signature header with base64-encoded HMAC-SHA256 of payload. Verify on receiving end.

Conditions: Use Jinja2 — {{ doc.grand_total > 10000 }}.

See references/webhooks-reference.md for complete handler examples.


HTTP Status Codes

CodeMeaningCommon Cause
200Success
400Bad requestValidation error
401UnauthorizedMissing or invalid auth
403ForbiddenNo permission for operation
404Not foundDocument does not exist
417Expectation failedServer exception (frappe.throw)
429Rate limitedToo many requests
500Server errorUnhandled exception

Critical Rules

  1. ALWAYS include Accept: application/json header in API requests
  2. ALWAYS add permission checks in @frappe.whitelist() methods
  3. ALWAYS validate and sanitize input in whitelisted methods
  4. ALWAYS use parameterized queries — NEVER string-interpolate SQL
  5. ALWAYS use timeout=30 on external requests calls
  6. ALWAYS store credentials in frappe.conf or env vars — NEVER hardcode
  7. ALWAYS verify webhook signatures with HMAC-SHA256
  8. ALWAYS paginate list responses — NEVER return unbounded result sets
  9. NEVER use allow_guest=True on state-changing endpoints
  10. NEVER log credentials or sensitive data
  11. NEVER use Administrator API keys for integrations — create dedicated API users

Anti-Patterns

Do NOTDo Instead
No permission check in whitelistfrappe.has_permission() before action
frappe.db.sql(f"...{user_input}")Parameterized %s queries
allow_guest=True + state changeRequire authentication
Return all records without limitPaginate with limit_page_length
Hardcode API credentialsfrappe.conf.get("api_key")
Synchronous heavy processingfrappe.enqueue() for long tasks
No timeout on external callsrequests.get(url, timeout=30)
Inconsistent response formatALWAYS return {"status": "...", "data": ...}

Version Differences

Featurev14v15v16
/api/resource/ (v1)YesYesYes
/api/v2/document/ (v2)NoYesYes
/api/v2/doctype/{dt}/metaNoYesYes
/api/v2/doctype/{dt}/countNoYesYes
limit alias parameterNoYesYes
PKCE for OAuth2LimitedYesYes
Server Script rate limitingNoYesYes
Doc method via v2 URLNoYesYes

Reference Files

FileContents
authentication-methods.mdToken, Session, OAuth2 with code examples
rest-api-reference.mdComplete REST CRUD with filters and pagination
rpc-api-reference.mdWhitelisted methods, frappe.call, frappe.xcall
webhooks-reference.mdWebhook config, security, handler examples
anti-patterns.mdCommon mistakes with fixes
examples.mdPython/JS/cURL client implementations

Related Skills

  • frappe-core-permissions — Permission system for API endpoints
  • frappe-core-database — Database queries behind API methods
  • frappe-syntax-hooks — Hook configuration for webhooks
  • frappe-syntax-controllers — Controller methods called via API

Verified against Frappe docs 2026-03-20 | Frappe v14/v15/v16

Bundled files

The model reads these on demand while the skill is loaded. They are exposed as readable files and are never executed.

Frequently asked questions

What does the Frappe Core Api AI skill do?

Use when building ERPNext/Frappe API integrations (v14/v15/v16) including REST API, RPC API, authentication, webhooks, and rate limiting. Covers external API calls, endpoint design, token/OAuth2/session authentication. Keywords: API integration, REST endpoint, webhook, token authentication,, how to connect, external API, send data to another system, API not working, 401 error. OAuth, frappe.call, external connection, rate limiting.

Why use Frappe Core Api on TypingMind?

Because you install it once and use it with any model. Frappe Core Api is plain Markdown rather than provider-specific code, so the same skill runs on GPT-5, Claude, Gemini, Grok, or a local model — and you can switch model mid-chat without it breaking. TypingMind runs on your own API keys, so you pay providers directly instead of a per-seat subscription, and your skills and chats stay in your own storage.

How do I install Frappe Core Api in TypingMind?

Open Plugins → Skills → Install from GitHub in TypingMind and paste https://github.com/Impertio-Studio/Frappe_Claude_Skill_Package/tree/main/skills/source/core/frappe-core-api. TypingMind reads its SKILL.md and bundles its files and installs it as a skill you can enable per chat.

Which AI models can use Frappe Core Api?

Any model you connect in TypingMind. AI skills are plain Markdown instructions rather than provider-specific code, so GPT, Claude, Gemini, Grok, and local models can all load this skill when a request matches it.

How many AI models can I use with Frappe Core Api?

As many as you like. As long as a model supports skills, you can use Frappe Core Api with it — GPT, Claude, Gemini, Grok, DeepSeek, Mistral, Llama and more — all on TypingMind with your own API keys.

Is the Frappe Core Api AI skill free?

Yes. It is published on GitHub by Impertio-Studio under the MIT license. You only pay your own AI provider for the tokens you use.

What are AI skills?

An AI skill is a reusable instruction bundle that teaches an AI model how to do one specific task. It follows the open Agent Skills format: a SKILL.md file with a name and description, plus any scripts, templates or reference files the model may need. The model reads the instructions only when your request matches the skill, so an installed skill costs nothing until it is used.

How are AI skills different from plugins or MCP servers?

A plugin or MCP server gives a model new tools to call — code that runs somewhere and returns a result. An AI skill gives the model knowledge and process instead: how to approach a task, which steps to follow, what good output looks like. Skills are plain Markdown, so they need no server, no API key and no runtime, and they work with any model.

View all

Set up your own AI workspace now

Get notified about new features and future giveaways by subscribing to our newsletter 👇