Install
openclaw skills install @pdparchitect/crmkitAn agent-first CRM, built for an AI agent to operate over a plain-text HTTP API. Headless by design (no UI). Responses are grepable plain text by default, or JSON when the Accept header requests it.
openclaw skills install @pdparchitect/crmkitYou are talking to crmkit, an agent-first CRM built for you to operate. It is headless - there is no UI; you (the agent) are the interface. Drive it with plain HTTP requests using your fetch/HTTP tool.
BASE_URL: https://api.crmkit.ai AUTH: send the header Authorization: Bearer on every request. FORMAT: responses are plain text by default (one labeled line per record). Add the header Accept: application/json (or ?format=json) for JSON.
Records are addressed by a stable handle like contact/c_ab12... - reuse that handle (or the bare id after the slash) in later calls. You can grep responses: each line stands alone.
Besides this plain HTTP API, crmkit is also a Model Context Protocol server at
POST https://api.crmkit.ai/mcp, so you can add it as a connector in clients like ChatGPT and
Claude. Point the client at https://api.crmkit.ai/mcp and it will walk the standard OAuth
flow automatically: it registers itself (dynamic client registration), opens a
crmkit sign-in page where the user enters their email and pastes the emailed
code, then receives a token. Over MCP, crmkit exposes a single generic tool,
request, that calls this same HTTP API (method + path + optional body) and
returns the same plain text shown below - so everything in this manual is
reachable from the connector. The token a connector receives is an ordinary
crmkit token - it appears in GET /tokens and can be revoked there like any
other session.
Your token operates inside ONE workspace (the one it was minted for). CRM data endpoints below always act on that workspace. To work in a different workspace you belong to, mint a token for it and send that token instead:
GET /workspaces list workspaces you belong to (with your role) POST /workspaces create one {"name":"My Team"} POST /workspaces/{id}/tokens mint a token scoped to that workspace ("switch") GET /workspaces/{id}/members members + pending invites POST /workspaces/{id}/invites add someone {"email":"x@acme.com","role":"member"} (admin only) POST /workspaces/{id}/members/{userId}/role change role {"role":"admin"|"member"} (admin only) DELETE /workspaces/{id}/members/{userId} remove a member (admin only) DELETE /workspaces/{id} delete the workspace + all its data (admin only)
Roles are "admin" and "member". Admins manage membership; members operate the CRM. Invites are by email: the invited person is emailed sign-in instructions and joins automatically the next time they authenticate. Switching workspace means switching which token you send - the CRM URLs below never change.
Each workspace/user has a plan with caps on how many objects you can create (contacts, companies, deals, members, workspaces). Creating past a cap returns "plan_limit_reached" (HTTP 403) - don't retry; tell the user the limit is hit. GET /whoami shows the current plan and usage (e.g. "contacts: 12 / 1000"), so check there to know the ceilings before bulk-creating.
Promoting a member to admin and deleting a workspace require an email
confirmation. Call the endpoint once: it returns "escalation_required" and emails
a code to your address. Ask the user for the code, then repeat the SAME request
with ?code= (or header X-Escalation-Code: ).
QUERY (the list endpoints: /contacts, /companies, /deals)
Filter by any allowed field as field=[op:]value ; repeated params are AND-ed:
?stage=lead (eq is the default operator)
?amount_cents=gte:100000 ops: eq ne gt gte lt lte like in is not
?stage=in:lead,qualified in: takes a comma-separated list
?follow_up_at=is:null is:null / not:null check empty / non-empty
*_at fields accept RFC3339, e.g. created_at=gte:2026-01-01T00:00:00Z
Fuzzy search across key fields: ?search=acme
Sort: ?sort=field or ?sort=-field (the - means descending).
Paginate: ?limit=N (default 50, max 200). When more rows remain, the response
ends with a line # next: (JSON: "next_cursor"); fetch the next page
with ?cursor= and keep the other params unchanged.
Unknown field/operator/value -> 400 listing what is allowed.
ENDPOINTS
GET /help this manual
GET /healthz liveness probe (no auth)
GET /readyz readiness probe - checks the database (no auth)
GET /whoami identity + current workspace behind the token
GET /tokens list your active tokens (sessions)
DELETE /tokens/{id} revoke one of your tokens (log out a session)
GET /contacts?&search=&sort=&limit=&cursor= list/query contacts (see QUERY)
POST /contacts create OR update by email (upsert) {"name":...,"email":...,"company_id":...,"stage":...,"tags":[...],"custom":{...}}
GET /contacts/{id} fetch one contact
PATCH /contacts/{id} update fields
DELETE /contacts/{id}?confirm= delete (two-step)
GET /contacts/{id}/activities activity log for a contact
POST /contacts/{id}/activities log {"kind":"call|email|meeting|note|task","body":...}
GET /companies?&search=&sort=&limit=&cursor= list/query companies (see QUERY)
POST /companies create OR update by domain (upsert) {"name":...,"domain":...,"custom":{...}}
GET /companies/{id} fetch one company
PATCH /companies/{id} update fields
DELETE /companies/{id}?confirm= delete (two-step)
GET /deals?&search=&sort=&limit=&cursor= list/query deals (see QUERY)
POST /deals create {"title":...,"amount_cents":...,"currency":"USD","stage":...,"contact_id":...,"company_id":...}
GET /deals/{id} fetch one deal
PATCH /deals/{id} update (e.g. {"stage":"won","status":"won"})
DELETE /deals/{id}?confirm= delete (two-step)
GET /reminders?days=&limit= due/overdue follow-ups (contacts + deals due now; ?days=N looks ahead)
GET /activities?contact=&deal=&limit= recent activities
GET /audit?limit= workspace audit log
REMINDERS (pull, not push)
There is no background notifier. To track a follow-up, set follow_up_at (RFC3339)
on a contact or deal, e.g. PATCH /contacts/{id} {"follow_up_at":"2026-06-10T09:00:00Z",
"follow_up_note":"Send the renewal quote"}. Then read what is due with
GET /reminders (overdue + due now) at the start of a session, or GET /reminders?days=7
to look a week ahead. Clear a follow-up by PATCHing follow_up_at to null.
EXAMPLES (curl)
begin login
curl -s -X POST https://api.crmkit.ai/auth/request -d '{"email":"you@example.com"}'
create a contact (note the bearer token)
curl -s -X POST https://api.crmkit.ai/contacts
-H 'Authorization: Bearer ck_...'
-d '{"name":"Jane Doe","email":"jane@acme.com","stage":"lead"}'
Custom fields: any keys you put under "custom" are stored as-is and returned on
the record, so the schema is extensible without server changes.