← Home

API documentation

Relm is API-first. Everything you can do in the dashboard, an agent can do over REST or the native MCP server - with the same bearer key.

Base URL & auth

The API lives at https://api.relmcrm.com. Authenticate every request with a workspace-scoped bearer key. Keys are shown once, SHA-256 hashed at rest, and come in live and test variants.

Authorization: Bearer relm_live_...

Mint keys in the dashboard. relm_test_ keys write to an isolated test dataset that is free and invisible to billing and the dashboard - build against them freely. Test mode is a sandbox, not storage: test records are automatically deleted 7 days after they are created.

Quickstart

Create your first contact in one call:

curl https://api.relmcrm.com/v1/contacts \
  -H "Authorization: Bearer relm_live_..." \
  -H "Content-Type: application/json" \
  -d '{ "email": "[email protected]", "first_name": "Ada", "last_name": "Lovelace" }'

Response:

{
  "id": "con_x8f3k2m9q2",
  "object": "contact",
  "email": "[email protected]",
  "first_name": "Ada",
  "last_name": "Lovelace",
  "created_at": "2026-07-09T12:00:00.000Z"
}

TypeScript SDK

Prefer types over curl? The official SDK is a zero-dependency wrapper over the same API.

npm i relmcrm
import { Relm } from "relmcrm";

const relm = new Relm(process.env.RELM_KEY!);

const ada = await relm.contacts.create({ email: "[email protected]", first_name: "Ada" });
await relm.deals.create({ title: "Acme - annual", stage: "lead" });

// page through everything
for await (const c of relm.contacts.all()) console.log(c.id, c.email);

Failed calls throw a RelmError you can read to self-correct: e.status, e.validOptions, e.hint. Works in Node 18+, Bun, Deno and the browser. On npm: npmjs.com/package/relmcrm.

Conventions

Core objects

Contacts, companies, deals and activities have full CRUD at /v1/<object> with matching MCP tools (relm_create, relm_list, relm_get, relm_update, relm_delete). The remaining surfaces have purpose-built endpoints and tools.

ObjectEndpointWhat it is
Contact/v1/contactsPeople. Email-optional - phone or LinkedIn-only leads are fine.
Company/v1/companiesAccounts. Contacts and deals link to them.
Deal/v1/dealsOpportunities in a pipeline + stage. Carries an amount.
Activity/v1/activitiesNotes, calls, emails, meetings. Backdatable via occurred_at.
Pipeline/v1/pipelinesNamed pipelines, each with ordered stages.
Automation/v1/automationsEvent-triggered "when X then Y" rules.
Sequence/v1/sequencesMulti-step drip sequences with auto-enroll and exit conditions.
Template/v1/templatesReusable email templates referenced by automations and sequences.
Webhook/v1/webhooksSubscribe an https endpoint to events. HMAC-signed, retried, dead-lettered.
Search/v1/searchCross-object search over contacts, companies and deals.
Schema/v1/schemaLive, self-describing object + field + enum registry.

Read the schema first

Before writing, an agent should GET /v1/schema to learn what objects, fields and enum values exist. If it then sends an unknown value, the error tells it exactly what is valid:

POST /v1/contacts   { "type": "prospect" }

422 Unprocessable Entity   (application/problem+json)
{
  "type": "https://relmcrm.com/errors/unknown_value",
  "title": "Unknown Value",
  "status": 422,
  "detail": "'prospect' is not a valid contact type.",
  "code": "unknown_value",
  "field": "contact type",
  "valid_options": ["lead", "customer"]
}

This is the "never confused" contract: agents self-correct from the error instead of failing blind or hallucinating a field. Need a new value? Create it - relm_create_enum_value, relm_create_field, relm_create_type.

Batch writes

Import many records in one round-trip with POST /v1/batch (or the relm_batch MCP tool). Each operation is metered individually - batching saves round-trips, not quota.

POST /v1/batch
{ "operations": [
  { "method": "create", "object": "contact", "data": { "email": "[email protected]" } },
  { "method": "create", "object": "deal",    "data": { "title": "Acme" } }
]}

Connect via MCP

Relm ships a native Model Context Protocol server at https://api.relmcrm.com/mcp (Streamable HTTP, request/response). Every CRM operation is a typed MCP tool. No key is needed to initialize or tools/list - the catalog is public so clients and directories can discover it; tools/call needs a credential.

Two ways to connect. If your client supports OAuth (most chat clients do), just point it at the server URL and it will walk you through signing in - the client registers itself, you approve in a browser, and you never handle a secret. If you would rather paste a key, or you are wiring up a server or a CI job, use an API key in a header:

{
  "mcpServers": {
    "relm": {
      "type": "http",
      "url": "https://api.relmcrm.com/mcp",
      "headers": { "Authorization": "Bearer relm_live_..." }
    }
  }
}

Then talk to it in plain language: "add these five leads and open a deal for each in the sales pipeline." The agent calls relm_describe_schema, then batches the writes. A single MCP POST can carry an array of tool calls; each is metered per call.

OAuth 2.1

For clients that authorize with OAuth, everything is discoverable - there is nothing to register by hand:

An unauthenticated tools/call returns 401 with a WWW-Authenticate header pointing at that metadata, which is how a client knows to offer you a Connect button. OAuth grants act on live data; test mode stays API-key only.

Connect via A2A

Relm also speaks Agent2Agent at https://api.relmcrm.com/a2a (JSON-RPC 2.0). Send a message/send whose data part is {"tool":"relm_...","arguments":{...}}; Relm runs it synchronously against the same validated tools and returns a terminal Task. The agent card is at /.well-known/agent-card.json.

OpenAPI

The full REST surface is described by a machine-readable OpenAPI 3.1 spec - import it into your client generator, Postman, or an agent toolchain.

Webhooks

Subscribe an https endpoint to CRM events. Register with POST /v1/webhooks (or relm_create_webhook) - the response returns a signing secret once. Events: contact.created, contact.updated, deal.created, deal.updated, deal.stage_changed (or ["*"] for all).

curl https://api.relmcrm.com/v1/webhooks \
  -H "Authorization: Bearer relm_live_..." -H "Content-Type: application/json" \
  -d '{ "url": "https://your.app/relm", "events": ["deal.stage_changed"] }'
# -> { "id": "wh_...", "secret": "whsec_...", ... }   (store the secret; shown once)

Each delivery is a JSON POST with headers Relm-Event, Relm-Delivery and Relm-Signature: t=<unix>,v1=<hmac>. Verify it by recomputing HMAC-SHA256 of "<t>.<raw body>" with your secret:

import crypto from "node:crypto";
function verify(secret, rawBody, header) {
  const { t, v1 } = Object.fromEntries(header.split(",").map(p => p.split("=")));
  const expected = crypto.createHmac("sha256", secret).update(`${t}.${rawBody}`).digest("hex");
  return crypto.timingSafeEqual(Buffer.from(v1), Buffer.from(expected));
}

Non-2xx or timeout retries with backoff (1m, 5m, 30m, 2h, 6h) and dead-letters after 6 attempts. Inspect recent attempts at GET /v1/webhooks/{id}/deliveries. Live-mode URLs must be public https (private, loopback and link-local hosts are rejected); test-mode keys may point at localhost.

Errors

Every error is RFC-9457 problem+JSON. type is a stable URI (https://relmcrm.com/errors/<code>) that resolves to a short reference page, with a human title, a machine code, and - where useful - valid_options and a suggestion.

StatuscodeMeaning
400bad_requestMalformed JSON or bad parameter.
401unauthorizedMissing or invalid API key.
403plan_limit / forbiddenPlan cap reached (Free allows 2 automations / 1 sequence) or action not permitted for this key.
404not_foundNo such record in this workspace/mode.
409conflict / idempotency_key_reusedDuplicate (e.g. email - returns the existing record), or a reused idempotency key.
412version_conflictRecord changed since you read it - re-fetch and reapply.
422unknown_value / unknown_field / validation_failed / invalid_referenceUnprocessable input - see valid_options and suggestion.
429rate_limited / quota_exceeded / spend_cap_reachedSlow down, out of monthly quota, or spend cap hit.

Rate limits & quotas

Requests are rate-limited per workspace per minute and counted against a monthly quota. Responses carry X-RateLimit-* and X-Quota-* headers.

PlanMonthly requestsOver the cap
Free1,000Hard-stops (429)
Pro - $29/mo100,000Metered overage, $0.0001/req
Scale - $249/mo2,000,000Metered overage, $0.0001/req

On paid plans you can set a hard spend cap; at $0 it behaves like Free and stops at the quota instead of billing overage. Test mode never counts.

Ready to build?

Mint a free key and point your agent at it.

Start free →