Skill125 estrellas del repoactualizado 2mo ago
frappe-errors-api
>
Instalar en Claude Code
Copiargit clone --depth 1 https://github.com/Impertio-Studio/Frappe_Claude_Skill_Package /tmp/frappe-errors-api && cp -r /tmp/frappe-errors-api/skills/source/errors/frappe-errors-api ~/.claude/skills/frappe-errors-apiDespués abre una sesión nueva de Claude Code; el skill carga automáticamente.
Definición
SKILL.md
# API Error Handling
For API implementation patterns see `frappe-core-api`. For permission errors see `frappe-errors-permissions`.
---
## HTTP Status Code Map: Error -> Cause -> Fix
| Code | Frappe Exception | When It Happens | Fix |
|------|-----------------|-----------------|-----|
| 200 | — | Success | — |
| 401 | `AuthenticationError` | Bad/expired token, wrong format | Check `Authorization: token key:secret` or `Bearer access_token` |
| 403 | `PermissionError` | Missing `@whitelist`, no role, no `allow_guest` | Add decorator or grant permission |
| 404 | `DoesNotExistError` | Wrong URL, doc not found, typo in endpoint path | Verify `/api/resource/:doctype/:name` or `/api/method/dotted.path` |
| 409 | `DuplicateEntryError` | Unique constraint violated | Check existing records before insert |
| 417 | `ValidationError` | `frappe.throw()` called | Fix validation logic or input data |
| 429 | `RateLimitExceededError` | Too many requests | Respect `Retry-After` header; throttle requests |
| 500 | `Exception` (unhandled) | Unhandled server error | Check Error Log; wrap in try/except |
| 503 | — | Server overloaded / maintenance | Retry with exponential backoff |
---
## Authentication Errors (401)
### Wrong Token Format
```
Error: HTTP 401 Unauthorized
Cause: Using "Bearer api_key:api_secret" instead of "token api_key:api_secret"
```
**Frappe uses TWO authentication formats — NEVER mix them:**
| Method | Header Format | When to Use |
|--------|--------------|-------------|
| API Key/Secret | `Authorization: token api_key:api_secret` | Server-to-server, scripts |
| OAuth Bearer | `Authorization: Bearer access_token` | OAuth 2.0 flows |
| Session Cookie | Cookie from `/api/method/login` | Browser-based apps |
```python
# WRONG — Bearer with API key:secret
headers = {"Authorization": f"Bearer {api_key}:{api_secret}"}
# CORRECT — token keyword for API key:secret
headers = {"Authorization": f"token {api_key}:{api_secret}"}
# CORRECT — Bearer for OAuth access tokens only
headers = {"Authorization": f"Bearer {oauth_access_token}"}
```
### Expired OAuth Token
```
Error: HTTP 401 after token was working
Cause: OAuth access_token expired
Fix: Use refresh_token to get new access_token
```
```python
def get_fresh_token(settings):
"""ALWAYS implement token refresh for OAuth integrations."""
if is_token_expired(settings.token_expiry):
response = requests.post(f"{settings.base_url}/api/method/frappe.integrations.oauth2.get_token", data={
"grant_type": "refresh_token",
"refresh_token": settings.get_password("refresh_token"),
"client_id": settings.client_id,
})
if response.status_code == 200:
data = response.json()
settings.access_token = data["access_token"]
settings.token_expiry = frappe.utils.add_to_date(None, seconds=data["expires_in"])
settings.save(ignore_permissions=True)
else:
frappe.throw(_("OAuth token refresh failed"), exc=frappe.AuthenticationError)
return settings.access_token
```
---
## Forbidden Errors (403)
### Missing @frappe.whitelist()
```
Error: HTTP 403 on /api/method/myapp.api.my_function
Cause: Function exists but lacks @frappe.whitelist() decorator
Fix: Add decorator — without it, NO external call is allowed
```
```python
# WRONG — Callable internally but returns 403 via REST
def my_function(name):
return frappe.get_doc("Item", name)
# CORRECT — Exposed to authenticated users
@frappe.whitelist()
def my_function(name):
return frappe.get_doc("Item", name)
# CORRECT — Exposed to everyone including unauthenticated
@frappe.whitelist(allow_guest=True)
def public_function():
return {"status": "ok"}
```
### Missing allow_guest for Public Endpoints
```
Error: HTTP 403 for unauthenticated requests
Cause: @frappe.whitelist() without allow_guest=True
Fix: Add allow_guest=True — but ALWAYS validate inputs
```
**NEVER use `allow_guest=True` without input validation** — these endpoints are exposed to the internet.
---
## Not Found Errors (404)
### Common URL Mistakes
| Wrong URL | Correct URL | Issue |
|-----------|-------------|-------|
| `/api/resource/SalesOrder/SO-001` | `/api/resource/Sales Order/SO-001` | Space in DocType name |
| `/api/method/myapp.my_function` | `/api/method/myapp.api.my_function` | Missing module path |
| `/api/resource/sales_order` | `/api/resource/Sales Order` | Wrong case / underscore |
| `/api/v2/document/Item/ITEM-001` [v14] | `/api/resource/Item/ITEM-001` | v2 API only in v15+ |
```python
# ALWAYS URL-encode DocType names with spaces
import urllib.parse
url = f"/api/resource/{urllib.parse.quote('Sales Order')}/{name}"
```
---
## Validation Errors (417)
Every `frappe.throw()` call returns HTTP 417 by default (unless a specific exception class is provided).
```python
# Returns 417 — generic validation error
frappe.throw(_("Amount must be positive"))
# Returns 417 — with explicit ValidationError type
frappe.throw(_("Amount must be positive"), exc=frappe.ValidationError)
# Returns 403 — PermissionError overrides to 403
frappe.throw(_("Access denied"), exc=frappe.PermissionError)
# Returns 404 — DoesNotExistError overrides to 404
frappe.throw(_("Not found"), exc=frappe.DoesNotExistError)
```
**ALWAYS use the specific exception class** so clients can handle error types correctly:
```python
# WRONG — all errors look the same to the client
frappe.throw(_("Customer not found")) # 417, generic
# CORRECT — client can distinguish 404 from validation error
frappe.throw(_("Customer not found"), exc=frappe.DoesNotExistError) # 404
```
---
## CSRF Token Errors
```
Error: HTTP 403 "CSRF token missing or invalid"
Cause: POST/PUT/DELETE request without X-Frappe-CSRF-Token header
```
**Rules:**
- ALWAYS include `X-Frappe-CSRF-Token` header for session-based (cookie) auth.
- Token-based auth (`Authorization: token ...`) does NOT require CSRF token.
- OAuth Bearer auth does