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...
---
name: crmkit
description: 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.
content_types: [text/plain, application/json]
capabilities: [contacts, companies, deals, activities, workspaces, members]
authentication:
type: bearer
scheme: email-otp
description: Email one-time-password - request a code, verify it, then send the returned token as a bearer credential on every request.
---
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.
don't have the plugin yet? install it then click "run inline in claude" again.
crmkit is a headless, agent-first CRM built for you to operate via plain HTTP requests. use this skill to create and manage contacts, companies, deals, and activities inside a workspace. responses are grepable plain text by default (one labeled line per record), or json when you request it. auth is email-otp: request a code, verify it, get a token, and send that token on every request. use crmkit when you need to track sales pipeline, log customer interactions, or query crm data without a ui.
base url
https://api.crmkit.ai (all requests go here)authentication (email-otp flow)
Authorization: Bearer <token>)external connections
https://api.crmkit.ai/mcp for dynamic oauth registration)workspace context
plan and limits
GET /whoami to see caps on contacts, companies, deals, members, workspaces)authentication flow (first-time or on token expiry)
POST https://api.crmkit.ai/auth/request with body {"email":"you@example.com"}.
retrieve the 6-digit otp code from your email inbox (check spam if needed).
POST https://api.crmkit.ai/auth/verify with body {"email":"you@example.com","code":"123456","token_name":"ChatGPT"}.
ck_...); save this token for all future requestsinclude the token on every subsequent request as header Authorization: Bearer <token>.
create or update a contact
POST https://api.crmkit.ai/contacts with json body containing contact fields.
{"name":"Jane Doe","email":"jane@acme.com","stage":"lead","company_id":"c_...","tags":["vip"],"custom":{"region":"EMEA"}}contact/c_<id> and line # created or # updated (if email already exists, it upserts)to update a specific contact you already have the handle for, use PATCH https://api.crmkit.ai/contacts/{id} with partial json.
{"stage":"qualified","follow_up_at":"2026-06-10T09:00:00Z","follow_up_note":"Send the renewal quote"}create or update a company
POST https://api.crmkit.ai/companies with json body.
{"name":"Acme Corp","domain":"acme.com","custom":{"industry":"SaaS"}}company/c_<id> and line # created or # updated (if domain already exists, it upserts)to update a specific company, use PATCH https://api.crmkit.ai/companies/{id} with partial json.
{"name":"Acme Corporation"}create a deal
POST https://api.crmkit.ai/deals with json body.
{"title":"Enterprise License Renewal","amount_cents":500000,"currency":"USD","stage":"negotiation","contact_id":"c_...","company_id":"c_..."}deal/d_<id> and line # createdto update a deal, use PATCH https://api.crmkit.ai/deals/{id} with partial json (e.g. {"stage":"won"}).
{"stage":"won","follow_up_at":null}query contacts, companies, or deals
GET https://api.crmkit.ai/contacts?stage=lead&sort=-created_at&limit=50 to list/filter records.
# next: <cursor>?stage=in:lead,qualified&amount_cents=gte:100000&created_at=gte:2026-01-01T00:00:00Zfetch next page by repeating the query with ?cursor=<cursor> and keeping other params unchanged.
log an activity
POST https://api.crmkit.ai/contacts/{id}/activities with json body.
{"kind":"call","body":"discussed renewal terms"} (kind: call, email, meeting, note, task)fetch activity log for a contact via GET https://api.crmkit.ai/contacts/{id}/activities.
manage reminders and follow-ups
set a follow-up by PATCHing a contact or deal with {"follow_up_at":"2026-06-10T09:00:00Z","follow_up_note":"Send the renewal quote"}.
pull due/overdue follow-ups via GET https://api.crmkit.ai/reminders (or ?days=7 to look 7 days ahead).
clear a follow-up by PATCHing {"follow_up_at":null}.
delete a record (two-step)
DELETE https://api.crmkit.ai/contacts/{id} (or /companies/{id} or /deals/{id}).
confirmation_required and a confirmation tokenconfirm deletion by repeating DELETE with ?confirm=<token>.
workspace and team management
GET https://api.crmkit.ai/workspaces to list all workspaces you belong to (with your role).
POST https://api.crmkit.ai/workspaces with {"name":"My Team"} to create a new workspace.
POST https://api.crmkit.ai/workspaces/{id}/tokens to mint a token scoped to a different workspace (switch workspaces).
POST https://api.crmkit.ai/workspaces/{id}/invites with {"email":"x@acme.com","role":"member"} to invite a user (admin only).
POST https://api.crmkit.ai/workspaces/{id}/members/{userId}/role with {"role":"admin"} to change a member role (admin only).
DELETE https://api.crmkit.ai/workspaces/{id}/members/{userId} to remove a member (admin only).
DELETE https://api.crmkit.ai/workspaces/{id} to delete the workspace and all its data (admin only).
check identity and plan
GET https://api.crmkit.ai/whoami to get your identity, current workspace, and plan usage.
GET https://api.crmkit.ai/tokens to list all your active sessions (tokens).
DELETE https://api.crmkit.ai/tokens/{id} to revoke a token (log out a session).
health checks
GET https://api.crmkit.ai/healthz for a liveness probe (no auth required).
GET https://api.crmkit.ai/readyz for a readiness probe (checks database; no auth required).
on http 401 (auth_required, invalid_token, or token_expired)
on http 403 plan_limit_reached
GET /whoami first to see usage before bulk-creating.on http 400 (invalid field, operator, or value)
upsert vs. update (contacts and companies)
escalation required (step-up for sensitive actions)
?code=<code> query param or header X-Escalation-Code: <code>.deletion confirmation
?confirm=<token> to actually delete. this prevents accidental deletes. you have 10 minutes to confirm; after that the token expires.response format (plain text vs json)
contact/c_ab12 | Jane Doe | jane@acme.com | lead). to get json, add header Accept: application/json or query param ?format=json to your request.mcp connector vs. plain http
https://api.crmkit.ai/mcp. the client registers itself, opens a sign-in page, and receives a token. all endpoints in this manual are reachable via the mcp request tool. responses are the same plain text.custom fields (extensible schema)
{"name":"Acme","custom":{"region":"EMEA","segment":"enterprise"}}.rate limits and network timeouts
GET /healthz).empty result sets
token inactivity and renewal
plain text response (default)
contact/c_ab12 | Jane Doe | jane@acme.com | lead)# next: <cursor>json response (when Accept: application/json or ?format=json)
records array and optional next_cursor string{"records":[{"id":"c_ab12","handle":"contact/c_ab12","name":"Jane Doe","email":"jane@acme.com","stage":"lead",...}],"next_cursor":"..."http status codes
GET /readyz; retry after brief delayerror response format
auth_required, plan_limit_reached, confirmation_required) and a hint telling you what to do nextrecord handles and ids
contact/c_<id>, company/c_<id>, deal/d_<id>, etc.timestamps
2026-01-15T14:30:00Zmoney