# Relm — full reference > API-first, AI-native CRM. Everything an LLM agent needs to model customer data. Base URL: https://relmcrm.com API version header: Relm-Version (echoed on every response) OpenAPI 3.1: /openapi.yaml Human docs: /docs ## Authentication Send `Authorization: Bearer ` on every request. - Live keys start with `sk_live_`. - Test keys start with `sk_test_` and are scoped to their own workspace mode. Create keys on /api-keys inside the dashboard. ## Conventions - Idempotency: send `Idempotency-Key: ` on any POST. Same key + same body within 24h → cached response. Different body → 409 idempotency_conflict. - Cursor pagination: list responses are `{ object: "list", data, has_more, next_cursor }`. Pass the cursor back as `?cursor=…`. Default limit 50, max 200. - Filters: `?filter[field][op]=value`. Ops: eq, neq, gt, gte, lt, lte, ilike, in. Shorthand `?email=ada@example.com` == `filter[email][eq]=ada@example.com`. - Rate limits: every response returns `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` (unix seconds). 429 responses include `Retry-After`. - Errors: `{ error: { code, message } }` with the matching HTTP status. ## Core resources ### Contacts (id prefix: con_) - POST /v1/contacts create - POST /v1/contacts/batch bulk upsert (≤500, dedup_on: ["email"]) - GET /v1/contacts list - GET /v1/contacts/:id retrieve - PATCH /v1/contacts/:id update - DELETE /v1/contacts/:id delete Fields: email, first_name, last_name, phone, company_id, metadata. ### Companies (cmp_) - POST/GET/PATCH/DELETE /v1/companies[/:id] Fields: name, domain, metadata. ### Deals (deal_) - POST/GET/PATCH/DELETE /v1/deals[/:id] Fields: title, value_cents, currency, stage, close_date, company_id, primary_contact_id, metadata. ### Activities (act_) - POST/GET/PATCH/DELETE /v1/activities[/:id] Fields: type, subject, body, contact_id, deal_id, company_id, occurred_at, metadata. ### Search - GET /v1/search?q=… full-text across entities ## Schema (custom fields) - GET /v1/schema/fields?object_type=contact - POST /v1/schema/fields - DELETE /v1/schema/fields/:id Types: text | number | date | bool | select. Values live in `metadata` and are validated on every write. Filter by them like any other field: `?filter[metadata->>plan]=pro`. ## Associations Polymorphic edges between any two records. - POST /v1/associations { from_type, from_id, to_type, to_id, label? } - GET /v1/associations?from_type=…&from_id=… - DELETE /v1/associations/:id ## Webhooks - POST/GET/PATCH/DELETE /v1/webhooks[/:id] - POST /v1/webhooks/:id/replay/:deliveryId replay a delivery Payloads are signed with HMAC-SHA256 using the endpoint's `whsec_…` secret. Header: `relm-signature: t=,v1=` where `v1 = HMAC_SHA256(secret, `${t}.${rawBody}`)`. Event types: contact|company|deal|activity × created|updated|deleted. Subscribe to `"*"` for all. Failed deliveries retry with exponential backoff up to 8 attempts. ## Object ID prefixes con_ (contact), cmp_ (company), deal_ (deal), act_ (activity), whep_ (webhook endpoint). ## Quick recipes ### Create a contact (curl) ``` curl -X POST $BASE/v1/contacts \ -A "relm-client" \ -H "Authorization: Bearer $RELM_KEY" \ -H "Idempotency-Key: create-ada-1" \ -H "Content-Type: application/json" \ -d '{"email":"ada@example.com","first_name":"Ada"}' ``` ### Bulk import with dedup ``` curl -X POST $BASE/v1/contacts/batch \ -A "relm-client" \ -H "Authorization: Bearer $RELM_KEY" \ -H "Content-Type: application/json" \ -d '{"contacts":[{"email":"a@x.com"},{"email":"b@x.com"}],"dedup_on":["email"]}' ``` ### Filter deals ``` GET /v1/deals?filter[stage]=won&filter[value_cents][gte]=1000000 ``` ### Verify a webhook (Node) ```js import crypto from "node:crypto"; const [t, v1] = req.headers["relm-signature"].split(",").map(p => p.split("=")[1]); const expected = crypto.createHmac("sha256", secret).update(`${t}.${rawBody}`).digest("hex"); if (!crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(v1))) throw new Error("bad sig"); ``` ## MCP (Model Context Protocol) Relm ships a spec-compliant MCP server so LLM agents (Claude, ChatGPT, Cursor, etc.) can drive the CRM natively. - Endpoint: `POST /mcp` - Auth: `Authorization: Bearer sk_live_...` (the same Relm API keys as REST). Every tool call is workspace-scoped and counts against the same monthly quota and rate limits as REST. - Transport: JSON-RPC 2.0 over Streamable HTTP (protocol version `2025-06-18`). - Handshake: `initialize` → `notifications/initialized` → `tools/list` → `tools/call`. ### Tools - `create_contact` — create a person. Set `company_id` for the free-plan link to a company. - `get_contact` — fetch a contact + recent activities + associations (associations require Team). - `update_contact` — patch a contact. - `list_contacts` — filter by `email`, `company_id`; supports `limit` + `cursor` pagination. - `create_company` — create an organization. - `get_company` — fetch a company. - `list_companies` — paginated list. - `create_deal` — create an opportunity. `company_id` + `primary_contact_id` link on every plan; use `create_association` for arbitrary edges (Team). - `get_deal` — fetch a deal. - `list_deals` — filter by `stage`, `company_id`; paginated. - `update_deal` — patch fields including `stage`, `value_cents`, `metadata`. - `move_deal_stage` — convenience wrapper for stage changes; fires `deal.stage_changed` webhook. - `log_activity` — record a note/email/call/meeting/task against a contact, deal, and/or company. - `list_activities` — filter by `contact_id` or `deal_id`; paginated. - `search` — full-text across contacts, companies, deals. - `create_association` — polymorphic edge between any two records (Team plan). - `list_pipelines` / `create_pipeline` / `update_pipeline` / `delete_pipeline` — manage the workspace's pipelines. - `list_pipeline_stages` / `create_pipeline_stage` / `update_pipeline_stage` / `delete_pipeline_stage` — manage stages within a pipeline (omit `pipeline` to target the default). - `create_automation` / `list_automations` / `get_automation` / `update_automation` / `delete_automation` — manage automation rules end-to-end. ## Pipelines & stages A workspace has one or more pipelines (like HubSpot/Pipedrive); each pipeline owns its own ordered set of stages. Every deal has `pipeline` (pipeline key) and `stage` (stage key within that pipeline). Omit `pipeline` on create to use the workspace's default pipeline. Pipelines - POST/GET /v1/pipelines { key, label, position?, is_default? } - GET/PATCH/DELETE /v1/pipelines/:key 409 cannot_delete_default / pipeline_in_use on DELETE Stages within a pipeline - POST/GET /v1/pipelines/:key/stages { key, label, position?, is_won?, is_lost? } - PATCH/DELETE /v1/pipelines/:key/stages/:stageKey 409 stage_in_use if a deal still uses it Backward-compat aliases (operate on the workspace's DEFAULT pipeline) - POST/GET /v1/pipeline/stages - PATCH/DELETE /v1/pipeline/stages/:key Defaults on new workspaces: pipeline `sales` with stages lead, qualified, proposal, won, lost. Changing a deal's stage emits `deal.stage_changed`; changing its pipeline emits `deal.pipeline_changed`. The `move_deal_stage` automation action accepts an optional `pipeline` to move the deal across pipelines. ## Automations Rules that run actions when events fire. Mode-scoped (test keys create test-mode rules), workspace-scoped, and loop-guarded (max depth 2). - POST/GET /v1/automations - GET/PATCH/DELETE /v1/automations/:id Body shape: ```json { "name": "Move to Replied on inbound email", "trigger_event": "activity.created", "enabled": true, "conditions": [{ "field": "type", "op": "eq", "value": "email" }], "actions": [{ "type": "move_deal_stage", "stage": "replied" }] } ``` Trigger events: `*`, `contact.created|updated|deleted`, `company.*`, `deal.*`, `activity.*`, `deal.stage_changed`. Conditions (ANDed): `{ field, op, value? }`. `field` is a dot-path into the event data (e.g. `new_stage`, `metadata.source`). Ops: `eq | neq | contains | exists | in`. Actions: - `{ type: "move_deal_stage", stage }` — moves the deal in context to a stage. - `{ type: "create_activity", activity_type: "note"|"email"|"call"|"meeting"|"task", subject?, body? }` — logs an activity carrying contact/deal/company context. - `{ type: "set_metadata", target?: "auto"|"contact"|"deal"|"company", key, value }` — merges a metadata field. - `{ type: "send_email", to, subject, html?, text?, from? }` — sends via the workspace's configured Resend integration; in test mode the send is simulated and never delivered. Runs are recorded in `automation_runs` with `status = success|error|skipped`. ## Email Bring your own Resend account. The workspace's API key is stored encrypted (Supabase Vault) and never returned by the API. - POST /v1/emails transactional send (from, to, subject, html or text). Test-mode keys simulate (no delivery). - GET /v1/integrations/email status: { configured, from_email, verified } - PUT /v1/integrations/email { api_key: "re_...", from_email } — stores the key in Vault - DELETE /v1/integrations/email disconnect - GET /v1/email/domains list sending domains in the connected Resend account - POST /v1/email/domains { name, region? } — returns Resend's created domain incl. DNS records (SPF/DKIM/DMARC) to publish - GET /v1/email/domains/:id fetch a single domain + current DNS status - POST /v1/email/domains/:id/verify trigger Resend to re-check DNS MCP tools mirror these: `get_email_integration`, `set_email_integration`, `send_email`, `list_email_domains`, `add_email_domain`, `get_email_domain`, `verify_email_domain`. Automations use `{ type: "send_email", to, subject, html?, text?, from? }`. In test mode the send is simulated. ## Changelog - 2026-07-02 — Docs & DX • New /changelog page with RSS feed at /changelog.rss. • Every response now echoes the API version as the Relm-Version header. • Expanded /llms.txt and new /llms-full.txt for Cursor, Claude Code, and other LLM crawlers. • Quickstart in /docs now shows curl, JavaScript (fetch), and Python (requests) side by side. - 2026-07-02 — Webhooks (delivered) • POST/GET /v1/webhooks to subscribe endpoints; each returns a whsec_… signing secret. • HMAC-SHA256 signatures on every payload (relm-signature: t=…,v1=…). • Automatic retry with exponential backoff up to 8 attempts; per-delivery replay endpoint. • Events for contact|company|deal|activity × created|updated|deleted, subscribe with "*" for all. • Deliveries dashboard at /webhooks with live status and replay button. - 2026-07-02 — Custom fields & associations • POST /v1/schema/fields — workspace-scoped custom fields on contact/company/deal/activity (text, number, date, bool, select). • Values live in metadata and are validated on every write. • POST /v1/associations — first-class polymorphic edges between any two records with an optional label. • New /schema page in the dashboard to manage custom fields visually. - 2026-07-02 — API completeness • Idempotency-Key header on every POST — 24h cache, 409 on body mismatch. • Cursor pagination on every list endpoint: { data, has_more, next_cursor }. • Typed filters: ?filter[field][op]=value with eq, neq, gt, gte, lt, lte, ilike, in. • POST /v1/contacts/batch — bulk upsert up to 500 rows with dedup_on: ["email"]. • Test-mode keys (sk_test_…) alongside live keys (sk_live_…). • X-RateLimit-* headers on every response; public OpenAPI 3.1 spec at /openapi.yaml. - 2026-07-01 — Relm launch • Multi-tenant workspaces with email + Google sign-in. • REST API for contacts, companies, deals, activities, and search under /v1/. • sk_live_ API keys, dashboard, request logs.