CRM Kit

API key required
Data & APIs

An 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.

Install

openclaw skills install crmkit

crmkit - Agent Operating Manual

You 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.

MCP CONNECTOR (chat clients)

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.

FIRST-TIME AUTH

  1. POST /auth/request {"email":"you@example.com"} -> a 6-digit login code is emailed to that address.
  2. Ask the user for the code.
  3. POST /auth/verify {"email":"you@example.com","code":"123456","token_name":"<who you are>"} -> returns a token. SAVE IT. Send it as Authorization: Bearer <token> from now on. If you can persist it in memory, do so; otherwise ask the user to keep it for next session. Set "token_name" to label this session - e.g. your client ("ChatGPT", "Claude", "Cursor") or its purpose. It appears in GET /tokens so the user can recognize and revoke sessions. Optional; defaults to "default". On any 401 (auth_required / invalid_token / token_expired), repeat this flow. Tokens are long-lived but expire after a period of inactivity (each use renews them), so a token in regular use keeps working; an idle one eventually dies.

CONVENTIONS

  • All request bodies are JSON. Only send fields you want to set/change.
  • PATCH performs a partial update: omitted fields are preserved.
  • DELETE is gated: the first call returns confirmation_required with a token; confirm with the user, then repeat with ?confirm=<token>.
  • Errors are instructive: they include a hint telling you what to do next.
  • Money is integer cents (amount_cents) plus a currency code.
  • POST /contacts and /companies UPSERT: if you include an email (contacts) or domain (companies) that already exists, the existing record is updated (merge of the fields you send) instead of creating a duplicate. The response says "# created" or "# updated". To update a specific record you already hold a handle for, use PATCH /contacts/{id}.

WORKSPACES & TEAMS

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.

PLANS & LIMITS

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.

STEP-UP (sensitive actions)

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>).

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: <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.

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?<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

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.