DenchClaw API Reference: Build Custom Integrations
DenchClaw REST API reference: authentication, entries CRUD, objects, fields, webhooks, documents, the Bridge API for Dench Apps, and curl examples for every endpoint.
DenchClaw API Reference: Build Custom Integrations
DenchClaw exposes a REST API that lets you build custom integrations, automate workflows, and connect external tools without touching the DuckDB file directly. Whether you're building a custom form that creates CRM entries, a dashboard that pulls deal data, or a script that enriches contacts, the REST API is your interface.
This reference covers every endpoint category with working curl examples, authentication, error handling, and when to use the API versus direct DuckDB access.
Overview#
The DenchClaw API runs locally at http://localhost:4242 by default (port is configurable). All endpoints return JSON. Authentication uses API keys generated by the CLI.
Base URL: http://localhost:4242/api/v1
For remote access (via your DenchClaw instance URL):
Base URL: https://your-instance.dench.com/api/v1
Authentication#
Generate an API key#
npx denchclaw api-key create --name "My Integration" --permissions read,writeThis outputs:
{
"key": "dench_live_abc123xyz...",
"name": "My Integration",
"permissions": ["read", "write"],
"created_at": "2026-03-26T18:00:00Z"
}Store this key securely. It won't be shown again.
Authenticate requests#
Pass the API key in the Authorization header:
curl -H "Authorization: Bearer dench_live_abc123xyz..." \
http://localhost:4242/api/v1/objectsPermission levels#
| Permission | Description |
|---|---|
read | Read entries, fields, objects, documents |
write | Create and update entries |
delete | Delete entries |
admin | Full access including webhooks and field management |
Core Endpoints#
Objects#
List all CRM objects (people, companies, deals, etc.):
# List all objects
curl -H "Authorization: Bearer $API_KEY" \
http://localhost:4242/api/v1/objects
# Response
{
"objects": [
{
"id": "obj_01",
"name": "people",
"label": "People",
"field_count": 12,
"entry_count": 847
},
{
"id": "obj_02",
"name": "companies",
"label": "Companies",
"field_count": 8,
"entry_count": 203
},
{
"id": "obj_03",
"name": "deals",
"label": "Deals",
"field_count": 10,
"entry_count": 156
}
]
}Fields#
Get fields for an object:
# List all fields on the people object
curl -H "Authorization: Bearer $API_KEY" \
http://localhost:4242/api/v1/objects/people/fields
# Response
{
"fields": [
{
"id": "fld_01",
"name": "name",
"label": "Name",
"type": "text",
"required": true
},
{
"id": "fld_02",
"name": "email",
"label": "Email",
"type": "email",
"required": false
},
{
"id": "fld_03",
"name": "status",
"label": "Status",
"type": "select",
"options": ["Lead", "Qualified", "Customer", "Churned"]
}
]
}Create a new field:
curl -X POST \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "lead_score",
"label": "Lead Score",
"type": "number"
}' \
http://localhost:4242/api/v1/objects/people/fieldsEntries (CRUD)#
List entries#
# Get all people entries (paginated)
curl -H "Authorization: Bearer $API_KEY" \
"http://localhost:4242/api/v1/objects/people/entries?limit=50&offset=0"
# Response
{
"entries": [
{
"id": "entry_abc123",
"object": "people",
"created_at": "2026-01-15T09:00:00Z",
"updated_at": "2026-03-10T14:30:00Z",
"fields": {
"name": "Sarah Chen",
"email": "sarah@acme.com",
"company_name": "Acme Corp",
"status": "Customer",
"phone": "+1-555-0123"
}
}
],
"total": 847,
"limit": 50,
"offset": 0
}Filter entries#
# Get all leads
curl -H "Authorization: Bearer $API_KEY" \
"http://localhost:4242/api/v1/objects/people/entries?filter[status]=Lead"
# Get entries created after a date
curl -H "Authorization: Bearer $API_KEY" \
"http://localhost:4242/api/v1/objects/people/entries?filter[created_after]=2026-01-01"
# Search by name
curl -H "Authorization: Bearer $API_KEY" \
"http://localhost:4242/api/v1/objects/people/entries?search=Sarah"Get a single entry#
curl -H "Authorization: Bearer $API_KEY" \
http://localhost:4242/api/v1/objects/people/entries/entry_abc123
# Response
{
"id": "entry_abc123",
"object": "people",
"created_at": "2026-01-15T09:00:00Z",
"fields": {
"name": "Sarah Chen",
"email": "sarah@acme.com",
"company_name": "Acme Corp",
"status": "Customer"
}
}Create an entry#
curl -X POST \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d '{
"fields": {
"name": "Marcus Webb",
"email": "marcus@startup.io",
"company_name": "StartupCo",
"status": "Lead",
"phone": "+1-555-9876",
"lead_source": "Conference"
}
}' \
http://localhost:4242/api/v1/objects/people/entries
# Response
{
"id": "entry_new456",
"object": "people",
"created_at": "2026-03-26T18:00:00Z",
"fields": { ... }
}Update an entry#
# Update specific fields (PATCH — partial update)
curl -X PATCH \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d '{
"fields": {
"status": "Qualified",
"last_contacted": "2026-03-26"
}
}' \
http://localhost:4242/api/v1/objects/people/entries/entry_abc123Delete an entry#
curl -X DELETE \
-H "Authorization: Bearer $API_KEY" \
http://localhost:4242/api/v1/objects/people/entries/entry_abc123
# Response
{
"deleted": true,
"id": "entry_abc123"
}Documents#
Documents are free-text markdown files linked to CRM entries.
# List documents for an entry
curl -H "Authorization: Bearer $API_KEY" \
http://localhost:4242/api/v1/objects/people/entries/entry_abc123/documents
# Create a document (note) on an entry
curl -X POST \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d '{
"title": "Call Notes - March 26",
"content": "# Call Notes\n\nDiscussed pricing. Sarah is interested in the Pro plan...",
"category": "call_notes"
}' \
http://localhost:4242/api/v1/objects/people/entries/entry_abc123/documents
# Get a document
curl -H "Authorization: Bearer $API_KEY" \
http://localhost:4242/api/v1/documents/doc_xyz789Webhooks#
Manage webhooks via the API:
# List all webhooks
curl -H "Authorization: Bearer $API_KEY" \
http://localhost:4242/api/v1/webhooks
# Create an outgoing webhook
curl -X POST \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d '{
"direction": "outgoing",
"object": "deals",
"event": "entry.updated",
"url": "https://hooks.slack.com/services/YOUR/WEBHOOK",
"name": "Deal Update Slack Alert",
"secret": "your-secret-token"
}' \
http://localhost:4242/api/v1/webhooks
# Delete a webhook
curl -X DELETE \
-H "Authorization: Bearer $API_KEY" \
http://localhost:4242/api/v1/webhooks/wh_123Request/Response Format#
All requests use JSON. All responses return JSON.
Standard response envelope#
{
"data": { ... },
"meta": {
"request_id": "req_abc123",
"timestamp": "2026-03-26T18:00:00Z"
}
}Pagination#
{
"entries": [ ... ],
"pagination": {
"total": 847,
"limit": 50,
"offset": 100,
"has_more": true
}
}Error Handling#
DenchClaw uses standard HTTP status codes:
| Status | Meaning |
|---|---|
| 200 | Success |
| 201 | Created |
| 400 | Bad Request (invalid payload) |
| 401 | Unauthorized (invalid API key) |
| 403 | Forbidden (insufficient permissions) |
| 404 | Not Found |
| 409 | Conflict (duplicate entry) |
| 429 | Rate Limited |
| 500 | Internal Server Error |
Error response format:
{
"error": {
"code": "FIELD_NOT_FOUND",
"message": "Field 'xyz' does not exist on object 'people'",
"details": {
"field": "xyz",
"object": "people"
}
}
}Rate Limits#
The local DenchClaw API has no rate limits (it's your own machine). For hosted/remote DenchClaw instances, rate limits apply:
- 1,000 requests/minute for read operations
- 100 requests/minute for write operations
- Rate limit headers:
X-RateLimit-Limit,X-RateLimit-Remaining,X-RateLimit-Reset
When rate limited, retry after the X-RateLimit-Reset timestamp.
The Bridge API (window.dench for Dench Apps)#
Dench Apps run inside DenchClaw's web UI and have access to window.dench, a JavaScript API that communicates with the platform without HTTP:
// In a Dench App (runs in the app's iframe context)
// Get current user/workspace info
const workspace = await window.dench.getWorkspace();
// Query entries
const leads = await window.dench.query({
object: 'people',
filter: { status: 'Lead' },
limit: 100
});
// Create an entry
const newEntry = await window.dench.createEntry('people', {
name: 'New Lead',
email: 'lead@example.com',
status: 'Lead'
});
// Update an entry
await window.dench.updateEntry('entry_abc123', {
status: 'Qualified'
});
// Run a raw SQL query (read-only)
const results = await window.dench.sql(
'SELECT status, COUNT(*) as count FROM v_people GROUP BY status'
);
// Show a notification
window.dench.notify('Entry updated successfully', 'success');The Bridge API is only available inside Dench App iframes — it's not accessible from external domains.
REST API vs Direct DuckDB Access#
| Use Case | REST API | DuckDB Direct |
|---|---|---|
| External integrations (forms, webhooks, 3rd party tools) | ✅ | ❌ |
| Custom Dench Apps | Use Bridge API | For complex queries |
| Python/Node.js scripts on the same machine | Either | ✅ Faster |
| Remote access | ✅ | ❌ (not over network) |
| Complex multi-join queries | ⚠️ (limited SQL support) | ✅ |
| Real-time data access | ✅ | ✅ |
| Bulk data operations (thousands of rows) | ⚠️ (batching needed) | ✅ Much faster |
For scripts running on the same machine as DenchClaw, direct DuckDB access is faster and more flexible. Use the REST API when the integration runs externally or needs to be portable.
SDK Availability#
DenchClaw doesn't have official SDKs yet, but the REST API is straightforward to wrap in any language.
JavaScript/TypeScript (unofficial):
npm install denchclaw-js # community packagePython (unofficial):
pip install denchclaw # community packageFor the official SDK roadmap, check github.com/DenchHQ/denchclaw.
FAQ#
Does the API work when DenchClaw is offline?
The REST API requires the DenchClaw server to be running (npx denchclaw start). Direct DuckDB access works regardless.
Can I use the API for external webhooks without an API key? Incoming webhooks have their own secret token system separate from the API. External tools post to webhook URLs authenticated by the webhook secret, not an API key.
Is there a GraphQL API? Not currently. The REST API is the primary external interface. For complex queries, DuckDB SQL is more powerful anyway.
How do I find an entry's ID?
Query the entries endpoint with a search filter, or use DuckDB: SELECT id, name FROM v_people WHERE email = 'sarah@example.com'.
Can the API access documents across all entries?
Yes. The /api/v1/documents endpoint lists all documents. Filter by linked_entry_id or category query parameters.
Ready to try DenchClaw? Install in one command: npx denchclaw. Full setup guide →
