Step 1: Create a scoped API key
Budget: 2 minutes. In the CRM Solid panel, go to Settings > API Keys > New key. Give the key a descriptive name such as production-sender or analytics-dashboard. Then select only the scopes that integration actually needs:
read:contacts- list, search, and export contacts.read:messages- stream chat history and inbox threads.read:analytics- pull funnel, sequence, and account stats.write:messages- send DMs and bulk broadcasts.write:contacts- create, update, tag, and segment contacts.manage:sequences- start, pause, and resume drip sequences.manage:webhooks- register and rotate webhook endpoints.
A key used only by an analytics dashboard should have read:contacts and read:analytics only. If that key is ever leaked it cannot send a single message. This is the most important security decision in any API integration.
After creating the key, copy it immediately. The full token is shown only once. Store it in an environment variable (e.g., CRMSOLID_KEY) and never commit it to source control. Live keys start with cs_live_; test keys are visually distinct so you can tell them apart at a glance.
Zero-downtime rotation: when you need to rotate a key, create the replacement key first, deploy the new value to your environment, then revoke the old key. Both keys are valid during the overlap window so there are no 401 errors during the cutover.
Step 2: Make your first authenticated request
Budget: 1 minute. Pass Authorization: Bearer cs_live_... on every request. The example below sends a Telegram DM using the POST /v1/messages/send endpoint:
curl -X POST https://api.crmsolid.com/v1/messages/send -H "Authorization: Bearer cs_live_a4f9_YOUR_KEY" -H "Idempotency-Key: $(uuidgen)" -H "Content-Type: application/json" -d '{
"account_id": 7421,
"to": "@sarah_miller",
"text": "Hey Sarah, your invite is ready - check your inbox."
}'A successful response is HTTP 200 with a JSON body:
{
"id": "msg_01HKQ9R2X3",
"status": "queued",
"eta": "2026-06-04T09:00:00Z"
}status: "queued" means the message is in the job worker queue and will be delivered within seconds. If you receive a 403 Forbidden the key is missing the write:messages scope; if you receive 401 Unauthorized the token is invalid or revoked. Both errors return an RFC 7807 problem-detail body with a stable code field you can branch on.
Step 3: Read rate-limit headers and back off
Default limits: 600 requests per minute per key. Every API response includes four headers:
X-RateLimit-Limit- the per-minute cap (default 600).X-RateLimit-Remaining- requests left in the current window.X-RateLimit-Reset- seconds until the window resets and Remaining is restored to Limit.Retry-After- only present on a 429 response; the number of seconds to wait before retrying.
The right backoff strategy reads these headers before each request and pauses when Remaining approaches zero, rather than waiting for a 429. For message-send workflows, Telegram itself enforces a separate 50 sends per day per account limit; the API surfaces this as a dedicated error code.
Step 4: Core v1 REST endpoints
The complete reference is at api.crmsolid.com/swagger. The three endpoints you will use most often:
Send a message
POST /v1/messages/send - requires write:messages. Body fields: account_id (the Telegram account to send from), to (username or numeric user ID), text. Supports template variables like {first_name} resolved at send time.
Create or upsert a contact
POST /v1/contacts - requires write:contacts. Upsert on Telegram username or email. Body accepts name, username, email, tags (array of strings), and any custom fields your workspace defines. Returns the contact ID.
curl -X POST https://api.crmsolid.com/v1/contacts -H "Authorization: Bearer cs_live_a4f9_YOUR_KEY" -H "Idempotency-Key: $(uuidgen)" -H "Content-Type: application/json" -d '{
"username": "@sarah_miller",
"name": "Sarah Miller",
"email": "[email protected]",
"tags": ["trial", "webinar-2026-06"]
}'Enroll a contact in a sequence
POST /v1/sequences/{id}/enroll - requires manage:sequences. Body: contact_id. The sequence must already exist in the panel; the API does not create sequences. Returns the enrollment record with a status of active.
Step 5: Add idempotency keys to every write
One rule: every write request needs an Idempotency-Key header. Generate a UUID for each logical operation and store it until the operation succeeds. On a retry with the same key, the server returns the original response and does not execute the operation again.
What this prevents in practice: a message-send request times out at the network layer after the server already queued it. Without idempotency, a retry sends the same DM twice. With idempotency, the server recognises the key, returns the original msg_01HKQ9R2X3 response, and the DM is sent exactly once.
The same applies to contact creation: a retry on a failed upsert does not create a duplicate contact record.
Step 6: Subscribe to outbound webhooks
Budget: 3 minutes. Webhooks let your system react to CRM events in real time rather than polling. Register an endpoint with POST /v1/webhooks (requires manage:webhooks):
{
"url": "https://your-app.example.com/webhooks/crm",
"events": [
"message.received",
"lead.scored",
"sequence.completed",
"contact.created"
],
"secret": "your-chosen-signing-secret"
}CRM Solid will POST to your URL immediately when a matching event occurs. Payloads are JSON with a top-level event field, an id, a created_at timestamp, and a data object specific to the event type. Delivery is attempted with automatic exponential-backoff retries; a delivery log in the panel lets you replay any event manually.
The full list of available events includes: message.sent, message.received, message.failed, contact.created, contact.tagged, lead.scored, sequence.started, sequence.paused, sequence.completed, job.completed, and subscription.upgraded.
Step 7: Verify the webhook signature
Every webhook POST includes an X-Signature: sha256=<hex> header. Verify it before trusting the payload:
// Node.js / Express example
const crypto = require('crypto');
app.post('/webhooks/crm', express.raw({ type: 'application/json' }), (req, res) => {
const secret = process.env.CRMSOLID_WEBHOOK_SECRET;
const sig = req.headers['x-signature'] || '';
const expected = 'sha256=' + crypto
.createHmac('sha256', secret)
.update(req.body) // raw Buffer, NOT parsed JSON
.digest('hex');
if (!crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected))) {
return res.status(401).send('invalid signature');
}
const event = JSON.parse(req.body);
// handle event.event ...
res.status(200).send('ok');
});Two important details: use the raw request body, not the parsed JSON object (JSON serialisation is not deterministic across environments). Use constant-time comparison (crypto.timingSafeEqual in Node.js, equivalent in other runtimes) to prevent timing attacks.
Return HTTP 200 as quickly as possible. If your handler takes longer than 10 seconds, CRM Solid marks the delivery as timed-out and retries. Enqueue the event for async processing and return 200 immediately.
Step 8: Connect the MCP server for AI agents
The Model Context Protocol (MCP) server exposes your CRM as a named tool set that an AI assistant can call directly. To configure it in Claude Desktop (or any MCP-compatible client), add a server entry pointing at the CRM Solid MCP endpoint with an API key:
// claude_desktop_config.json (or equivalent MCP config)
{
"mcpServers": {
"crmsolid": {
"command": "npx",
"args": ["-y", "mcp-remote", "https://api.crmsolid.com/mcp"],
"env": {
"CRMSOLID_API_KEY": "cs_live_a4f9_YOUR_KEY"
}
}
}
}The MCP server is safe-write oriented. Read tools (list contacts, read messages, pull analytics) are always available when the key has the matching read scopes. Write tools (send message, create contact, enroll in sequence) require the corresponding write scopes and surface a permission error if the key does not have them.
Scope your MCP key to exactly what the agent needs. A summarization or reporting agent works well with read:contacts and read:messages only. An outreach agent additionally needs write:messages and optionally manage:sequences. Never grant manage:webhooks to a general-purpose chat assistant.
Note on the .NET SDK: the CrmSolid.Client NuGet package provides typed C# methods for every v1 endpoint. It handles Bearer auth, auto-generates idempotency keys per call, and implements the rate-limit backoff loop. The SDK wraps the same REST contract described in this guide; every endpoint path, scope, and header applies identically.