Install
openclaw skills install 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 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 <token> 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=<code> (or header X-Escalation-Code: <code>).
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: <cursor> (JSON: "next_cursor"); fetch the next page with ?cursor=<cursor> and keep the other params unchanged. Unknown field/operator/value -> 400 listing what is allowed.
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?<filters>&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?<filters>&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?<filters>&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
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.
curl -s -X POST https://api.crmkit.ai/auth/request -d '{"email":"you@example.com"}'
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.