ReactiveCRM Integration Guide
Technical documentation for companies (tenants) that want to build their own integration with ReactiveCRM: ingesting leads and clients, creating deals, sending marketing events, and exporting data.
This document covers only the APIs most commonly used for external integrations. Internal reference data and admin service endpoints are intentionally out of scope.
1. Integration Overview
A typical integration flow looks like this:
Your system (backend / server-side proxy)
|
| 1. POST /api/auth/login (obtain access + refresh tokens)
v
ReactiveCRM
| 2. References: GET /api/users/paged, GET /api/statuses, GET /api/stages, GET /api/lead-sources
| (obtain UUIDs for ownerId, authorId, statusId, etc.)
v
| 3. Core operations:
| POST /api/leads/create — create a lead
| POST /api/clients/create — create a client
| POST /api/contacts/create — create a contact
| POST /api/deals/create — create a deal
| POST /api/dashboard/marketing/events — send a marketing event
| POST /api/leads/import/batch — bulk import leads
v
| 4. Read / export:
| GET /api/leads/paged — paged read with filters
| GET /api/export/leads — NDJSON export of all leads
Key security rule: never call the CRM API directly from an end-user's browser. Keep the CRM token and internal identifiers on your backend (or a serverless function).
2. Authentication
2.1. Obtaining tokens
The authentication endpoints do not require a token:
| Method | URL | Description |
|---|---|---|
POST | /api/auth/login | Login with username + password, returns access + refresh tokens |
POST | /api/auth/refresh | Exchange a refresh token for a new token pair |
POST | /api/auth/logout | Revoke a refresh token |
curl -X POST "$CRM_BASE_URL/api/auth/login" \
-H "Content-Type: application/json" \
-d '{"username": "integration", "password": "secret123"}'
{
"token": "eyJhbGciOiJIUzI1NiJ9...",
"refreshToken": "a8f3k2m9Qx7Tt1Vv...",
"tokenType": "Bearer",
"user": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"firstName": "Integration",
"lastName": "Bot",
"tenantId": "660e8400-e29b-41d4-a716-446655440001"
},
"tenant": {
"id": "660e8400-e29b-41d4-a716-446655440001",
"name": "Zunga Corp"
}
}
2.2. Token handling rules
- Access token (
token) — a JWT that lives 1 hour. Send it with every request:Authorization: Bearer <token>. - Refresh token (
refreshToken) — a random string that lives 7 days. Use it to obtain a new token pair. - On every
/api/auth/refresh, the server rotates the refresh token: the old one is deleted and a new one is issued. Persist both new values. - A request with an expired access token returns
401. In that case, refresh and retry the original request — do not log in again.
2.3. Base settings
CRM_BASE_URL=https://crm.example.com
All examples below assume the headers:
Authorization: Bearer <CRM_ACCESS_TOKEN>
Content-Type: application/json
3. Paged API Conventions
All list endpoints of the form GET /api/*/paged share the same conventions:
| Parameter | Type | Default | Description |
|---|---|---|---|
page | integer | 0 | Page number, zero-based |
size | integer | per endpoint | Page size |
sort | string | createdAt,desc | Sorting in field,direction format. Direction: asc or desc |
Paged response shape
{
"content": [ { "..." } ],
"page": 0,
"size": 20,
"totalElements": 137,
"totalPages": 7
}
Date filtering
Use fieldFrom / fieldTo params for ranges (ISO 8601):
createdAtFrom=2026-01-01T00:00:00Z
createdAtTo=2026-01-31T23:59:59Z
Sorting
Always ?sort=field,direction:
?sort=createdAt,desc
?sort=name,asc
Some filter fields have dedicated range params (e.g.,
dealAmountFrom/dealAmountTo).
Filtering semantics
- All filtering and sorting is performed server-side (no need to download everything and filter client-side).
- Multiple filters are combined with AND.
- Text (substring) filters use case-insensitive search (ILIKE).
- The universal
searchfilter searches across multiple fields with OR.
4. References (obtaining UUIDs)
Before creating entities, obtain the UUIDs of related reference data. The main ones:
4.1. Users (GET /api/users/paged)
Used for ownerId, authorId, developingManagerIds.
curl "$CRM_BASE_URL/api/users/paged?page=0&size=50&sort=createdAt,asc" \
-H "Authorization: Bearer $CRM_ACCESS_TOKEN"
Example response (content fragment):
[
{
"id": "7a786a66-3617-48c1-921d-97f4aaefcd35",
"firstName": "Roman",
"lastName": "Posledovskiy",
"username": "roman",
"email": "roman@example.com",
"tenantId": "660e8400-e29b-41d4-a716-446655440001"
}
]
4.2. Statuses (GET /api/statuses)
Used for statusId of leads, deals, and deal-party clients. Returns an array:
curl "$CRM_BASE_URL/api/statuses" \
-H "Authorization: Bearer $CRM_ACCESS_TOKEN"
[
{
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"name": "New",
"entityType": "LEAD"
},
{
"id": "b2c3d4e5-f6a7-8901-bcde-f23456789012",
"name": "In progress",
"entityType": "STAGE"
}
]
4.3. Stages (GET /api/stages)
Used for the stageId of a deal:
curl "$CRM_BASE_URL/api/stages" \
-H "Authorization: Bearer $CRM_ACCESS_TOKEN"
4.4. Lead sources (GET /api/lead-sources)
Returns a tree of lead sources. Used for a lead's leadSourceId:
curl "$CRM_BASE_URL/api/lead-sources" \
-H "Authorization: Bearer $CRM_ACCESS_TOKEN"
[
{
"id": "cccc0001-0000-0000-0000-000000000001",
"name": "Website",
"children": [
{
"id": "cccc0001-0000-0000-0000-000000000002",
"name": "Feedback form",
"children": []
}
]
}
]
5. Leads
5.1. Create a lead (POST /api/leads/create)
curl -X POST "$CRM_BASE_URL/api/leads/create" \
-H "Authorization: Bearer $CRM_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"message": "Website inquiry: consultation",
"ownerId": "7a786a66-3617-48c1-921d-97f4aaefcd35",
"authorId": "7a786a66-3617-48c1-921d-97f4aaefcd35",
"leadSourceId": "cccc0001-0000-0000-0000-000000000002",
"statusId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"quality": "WARM"
}'
Request fields:
| Field | Type | Required | Description |
|---|---|---|---|
message | string | no | Message / inquiry text |
ownerId | uuid | yes | Responsible user |
authorId | uuid | yes | User who created the lead |
clientId | uuid | no | Related client |
contactId | uuid | no | Related contact |
leadSourceId | uuid | no | Lead source tree node |
statusId | uuid | no | Status |
quality | enum | no | HOT, WARM, COOL, COLD |
Example 201 Created response:
{
"id": "22222222-2222-2222-2222-222222222222",
"message": "Website inquiry: consultation",
"status": {
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"name": "New"
},
"quality": "WARM",
"dealId": null,
"createdAt": "2026-09-05T20:00:00Z",
"updatedAt": "2026-09-05T20:00:00Z",
"version": 0,
"author": {
"id": "7a786a66-3617-48c1-921d-97f4aaefcd35",
"firstName": "Roman",
"lastName": "Posledovskiy"
},
"owner": {
"id": "7a786a66-3617-48c1-921d-97f4aaefcd35",
"firstName": "Roman",
"lastName": "Posledovskiy"
}
}
Keep the returned
id— it is needed for the marketing eventleadId(see section 8).
5.2. Paged read (GET /api/leads/paged)
curl "$CRM_BASE_URL/api/leads/paged?page=0&size=20&createdAtFrom=2026-09-01T00:00:00Z&sort=createdAt,desc" \
-H "Authorization: Bearer $CRM_ACCESS_TOKEN"
Key filters:
| Parameter | Type | Mode | Description |
|---|---|---|---|
statusId | uuid | exact | Filter by status |
leadSourceId | uuid | exact | Filter by lead source |
quality | enum | exact | HOT, WARM, COOL, COLD |
message | string | ILIKE | Substring in message |
search | string | ILIKE (OR) | Search across message, contact, email, phone |
contactEmail | string | ILIKE | By contact email |
contactPhone | string | ILIKE | By contact phone |
company | string | ILIKE | By company name |
clientId | uuid | exact | By client ID |
createdAtFrom / createdAtTo | date-time | range | By creation date |
updatedAtFrom / updatedAtTo | date-time | range | By update date |
createdBy | uuid | exact | By lead author |
There are also filters for the related deal: dealStatusId, dealStageId, dealAmountFrom/dealAmountTo, dealProbabilityFrom/dealProbabilityTo.
Sorting: createdAt, updatedAt, message, quality, status/source, and others (field,direction format).
5.3. Get a single lead (GET /api/leads/{id})
curl "$CRM_BASE_URL/api/leads/$LEAD_ID" \
-H "Authorization: Bearer $CRM_ACCESS_TOKEN"
5.4. Bulk lead import (POST /api/leads/import/batch)
For bulk ingestion of a large number of leads, use the batch endpoint. It accepts an array of leads, creates an import-history record, and allows rolling back the whole import.
curl -X POST "$CRM_BASE_URL/api/leads/import/batch" \
-H "Authorization: Bearer $CRM_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"fileName": "leads-2026-09-05.csv",
"leads": [
{
"message": "Inquiry #1",
"ownerId": "7a786a66-3617-48c1-921d-97f4aaefcd35",
"authorId": "7a786a66-3617-48c1-921d-97f4aaefcd35",
"quality": "WARM"
},
{
"message": "Inquiry #2",
"ownerId": "7a786a66-3617-48c1-921d-97f4aaefcd35",
"authorId": "7a786a66-3617-48c1-921d-97f4aaefcd35",
"quality": "COOL"
}
]
}'
Example response:
{
"importId": "44444444-4444-4444-4444-444444444444",
"imported": 2,
"total": 2,
"skipped": []
}
imported— how many were created.skipped— array of skipped rows withindex(0-based) andreason.
History and rollback:
# Import history
curl "$CRM_BASE_URL/api/leads/import/history" \
-H "Authorization: Bearer $CRM_ACCESS_TOKEN"
# Rollback an import
curl -X POST "$CRM_BASE_URL/api/leads/import/rollback/$IMPORT_ID" \
-H "Authorization: Bearer $CRM_ACCESS_TOKEN"
5.5. Quick website lead (POST /api/site/leads)
For website forms there is a single combined endpoint that atomically creates the contact, its primary phone and email, the lead, the contact–lead link, and the marketing event in one request — with idempotency via externalId.
See the dedicated guide: site-lead-multi-contract.md.
6. Clients and Contacts
6.1. Create a client (POST /api/clients/create)
curl -X POST "$CRM_BASE_URL/api/clients/create" \
-H "Authorization: Bearer $CRM_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"type": "COMPANY",
"name": "Romashka LLC",
"taxId": "7700000001",
"regNumber": "1234567890",
"country": "RU",
"phone": "+7-495-000-00-01",
"email": "info@romashka.ru",
"website": "https://romashka.ru",
"ownerId": "7a786a66-3617-48c1-921d-97f4aaefcd35",
"authorId": "7a786a66-3617-48c1-921d-97f4aaefcd35"
}'
Key fields:
| Field | Type | Applies to | Description |
|---|---|---|---|
type | enum | all | INDIVIDUAL or COMPANY (required) |
name | string | all | Display name (required) |
firstName / lastName | string | INDIVIDUAL | First/last name |
taxId | string | COMPANY | Tax ID (INN) |
regNumber | string | COMPANY | Registration number |
legalAddress | string | COMPANY | Legal address |
phone / email / website | string | all | Contacts |
country | string | all | ISO country code |
ownerId | uuid | all | Responsible user |
authorId | uuid | all | Author |
developingManagerIds | uuid[] | all | Developing managers |
6.2. Paged client read (GET /api/clients/paged)
curl "$CRM_BASE_URL/api/clients/paged?page=0&size=20&type=COMPANY&search=Romashka&sort=name,asc" \
-H "Authorization: Bearer $CRM_ACCESS_TOKEN"
Key filters: name, type (INDIVIDUAL/COMPANY), country, taxId, email, phone, lastName (individuals), search (by name/taxId/phone), createdAtFrom/createdAtTo, createdBy, developingManagerId.
6.3. Duplicate check (GET /api/clients/duplicates)
Before creating a client, check whether a similar one already exists:
# By tax ID and name
curl "$CRM_BASE_URL/api/clients/duplicates?type=COMPANY&taxId=7700000001&name=Romashka" \
-H "Authorization: Bearer $CRM_ACCESS_TOKEN"
6.4. Create a contact (POST /api/contacts/create)
curl -X POST "$CRM_BASE_URL/api/contacts/create" \
-H "Authorization: Bearer $CRM_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"firstName": "Ivan",
"lastName": "Petrov",
"dateOfBirth": "1990-05-15",
"gender": "MALE",
"countryCode": "RU",
"ownerId": "7a786a66-3617-48c1-921d-97f4aaefcd35",
"authorId": "7a786a66-3617-48c1-921d-97f4aaefcd35"
}'
Fields: firstName (required), lastName, patronymicName, dateOfBirth (date), gender (MALE/FEMALE), countryCode, ownerId, authorId.
6.5. Paged contact read (GET /api/contacts/paged)
curl "$CRM_BASE_URL/api/contacts/paged?page=0&size=20&search=Petrov&sort=createdAt,desc" \
-H "Authorization: Bearer $CRM_ACCESS_TOKEN"
7. Deals
7.1. Create a deal (POST /api/deals/create)
curl -X POST "$CRM_BASE_URL/api/deals/create" \
-H "Authorization: Bearer $CRM_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "Server hardware delivery",
"clientId": "9f128931-69fd-493a-9a08-be5be0e6603d",
"statusId": "b2c3d4e5-f6a7-8901-bcde-f23456789012",
"stageId": "dddd0001-0000-0000-0000-000000000001",
"probability": 60,
"amount": 150000.00,
"ownerId": "7a786a66-3617-48c1-921d-97f4aaefcd35",
"authorId": "7a786a66-3617-48c1-921d-97f4aaefcd35"
}'
Key fields:
| Field | Type | Required | Description |
|---|---|---|---|
name | string | yes | Deal name |
clientId | uuid | yes | Client ID |
statusId | uuid | no | Status (stage status) |
stageId | uuid | no | Sales stage |
probability | integer | no | Probability 0-100 |
amount | number | no | Amount |
plannedAmount | number | no | Planned amount |
discountPercent | integer | no | Discount 0-100 |
startDate / expectedCloseDate | date-time | no | Dates |
ownerId | uuid | no | Owner |
leadId | uuid | no | Related lead |
contactId | uuid | no | Related contact |
productIds | uuid[] | no | Products (simplified format) |
dealProducts | array | no | Deal line items (full format) |
dealParties | array | no | Deal parties |
7.2. Paged deal read (GET /api/deals/paged)
curl "$CRM_BASE_URL/api/deals/paged?page=0&size=20&stageKind=OPEN&sort=createdAt,desc" \
-H "Authorization: Bearer $CRM_ACCESS_TOKEN"
Key filters: name (ILIKE), clientId, clientName, statusId, stageId, stageKind (OPEN/WON/LOST), ownerId, search, startDateFrom/startDateTo, expectedCloseDateFrom/expectedCloseDateTo, actualCloseDateFrom/actualCloseDateTo, createdAtFrom/createdAtTo.
8. Marketing Events (Attribution)
To send UTM tags, ad identifiers, and traffic sources, use:
POST /api/dashboard/marketing/events
curl -X POST "$CRM_BASE_URL/api/dashboard/marketing/events" \
-H "Authorization: Bearer $CRM_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"eventType": "lead_submitted",
"occurredAt": "2026-09-05T20:00:00Z",
"visitorId": "visitor-8b7f",
"sessionId": "session-31ac",
"leadId": "22222222-2222-2222-2222-222222222222",
"externalId": "site-form-submit-20260905-000123",
"source": "website",
"channel": "paid",
"utmSource": "google",
"utmMedium": "cpc",
"utmCampaign": "summer-consulting",
"utmContent": "banner-a",
"utmTerm": "crm consultation",
"gclid": "EAIaIQobChMI-example",
"landingUrl": "https://www.example.com/consultation",
"referrer": "https://www.google.com/",
"metadata": {
"formName": "consultation",
"pageType": "landing"
}
}'
Example response:
{
"accepted": true,
"duplicate": false,
"eventId": "33333333-3333-3333-3333-333333333333"
}
Rules:
- Build a stable
externalId(UUID or a unique requestId). Re-sending with the sameexternalIdfor the samesource/tenant returnsduplicate: true— that is not an error. - Do not generate a new
externalIdon retry. - Do not put personally identifiable information (email, phone, name, message text) into
metadata. Only technical attributes such as form name or page type.
For details, see site-leads-integration-guide.md.
9. Data Export (NDJSON)
Export endpoints return all records of the tenant in NDJSON format (one JSON object per line, \n-separated).
| Endpoint | Description |
|---|---|
GET /api/export/leads | All tenant leads |
GET /api/export/clients | All tenant clients |
GET /api/export/contacts | All tenant contacts |
curl "$CRM_BASE_URL/api/export/leads" \
-H "Authorization: Bearer $CRM_ACCESS_TOKEN"
Example response (line stream):
{"id":"550e8400-e29b-41d4-a716-446655440001","message":"Website inquiry","quality":"WARM","statusName":"New","createdAt":"2026-08-20T10:00:00+03:00","ownerName":"Ivan Ivanov","clientName":"Romashka LLC","contactFirstName":"Petr","contactLastName":"Petrov","leadSourceName":"Paid search"}
{"id":"550e8400-e29b-41d4-a716-446655440002","message":"Phone call","quality":"COOL","statusName":"In progress","createdAt":"2026-08-19T09:00:00+03:00","ownerName":"Maria Smirnova","clientName":null}
Important: NDJSON is not a valid JSON array. Read the response line by line via ReadableStream (do not call JSON.parse() on the whole response).
10. End-to-End Integration Example
The following scenario creates a website lead, links a contact and a client, creates a deal, and sends a marketing event.
CRM_BASE_URL=https://crm.example.com
CRM_TOKEN="<access-token>"
# 1. Login
curl -s -X POST "$CRM_BASE_URL/api/auth/login" \
-H "Content-Type: application/json" \
-d '{"username": "integration", "password": "secret123"}' | tee /tmp/login.json
CRM_TOKEN=$(jq -r '.token' /tmp/login.json)
OWNER_ID=$(curl -s "$CRM_BASE_URL/api/users/paged?page=0&size=50" \
-H "Authorization: Bearer $CRM_TOKEN" | jq -r '.content[0].id')
# 2. Create a client
curl -s -X POST "$CRM_BASE_URL/api/clients/create" \
-H "Authorization: Bearer $CRM_TOKEN" \
-H "Content-Type: application/json" \
-d "{
\"type\": \"COMPANY\",
\"name\": \"Romashka LLC\",
\"taxId\": \"7700000001\",
\"country\": \"RU\",
\"ownerId\": \"$OWNER_ID\",
\"authorId\": \"$OWNER_ID\"
}" | tee /tmp/client.json
CLIENT_ID=$(jq -r '.id' /tmp/client.json)
# 3. Create a contact
curl -s -X POST "$CRM_BASE_URL/api/contacts/create" \
-H "Authorization: Bearer $CRM_TOKEN" \
-H "Content-Type: application/json" \
-d "{
\"firstName\": \"Ivan\",
\"lastName\": \"Petrov\",
\"countryCode\": \"RU\",
\"ownerId\": \"$OWNER_ID\",
\"authorId\": \"$OWNER_ID\"
}" | tee /tmp/contact.json
CONTACT_ID=$(jq -r '.id' /tmp/contact.json)
# 4. Create a lead
curl -s -X POST "$CRM_BASE_URL/api/leads/create" \
-H "Authorization: Bearer $CRM_TOKEN" \
-H "Content-Type: application/json" \
-d "{
\"message\": \"Website inquiry\",
\"clientId\": \"$CLIENT_ID\",
\"contactId\": \"$CONTACT_ID\",
\"ownerId\": \"$OWNER_ID\",
\"authorId\": \"$OWNER_ID\",
\"quality\": \"WARM\"
}" | tee /tmp/lead.json
LEAD_ID=$(jq -r '.id' /tmp/lead.json)
# 5. Create a deal linked to the lead
curl -s -X POST "$CRM_BASE_URL/api/deals/create" \
-H "Authorization: Bearer $CRM_TOKEN" \
-H "Content-Type: application/json" \
-d "{
\"name\": \"Consultation for Ivan Petrov\",
\"clientId\": \"$CLIENT_ID\",
\"leadId\": \"$LEAD_ID\",
\"contactId\": \"$CONTACT_ID\",
\"amount\": 50000.00,
\"ownerId\": \"$OWNER_ID\",
\"authorId\": \"$OWNER_ID\"
}"
# 6. Send a marketing event
curl -s -X POST "$CRM_BASE_URL/api/dashboard/marketing/events" \
-H "Authorization: Bearer $CRM_TOKEN" \
-H "Content-Type: application/json" \
-d "{
\"eventType\": \"lead_submitted\",
\"occurredAt\": \"$(date -u +%Y-%m-%dT%H:%M:%SZ)\",
\"leadId\": \"$LEAD_ID\",
\"externalId\": \"request-$(uuidgen)\",
\"source\": \"website\",
\"channel\": \"direct\",
\"landingUrl\": \"https://www.example.com/form\",
\"metadata\": {\"formName\": \"consultation\"}
}"
11. Idempotency and Error Handling
- Stable identifier: generate your own
externalId(UUID or requestId) for each submission. Re-sending the sameexternalIdmust not create a duplicate (for marketing events this is handled by the server —duplicate: true). - Retry with the same identifier: on a network error, retry with the same
externalId; do not generate a new one. - Do not blindly duplicate: if a create request times out, first verify the result (using your own requestId or by finding the created entity) instead of creating a new one.
400: invalid payload — retrying without changes will not help; fix the data.401: expired access token — call/api/auth/refreshand retry the request.403: no permission — check role and tenant.5xx/timeout: transient error — retry with the sameexternalId(for leads, use a queue).
12. Quick Start
- Authenticate via
POST /api/auth/login; storetokenandrefreshToken. - Add
Authorization: Bearer <token>to every request. On401, refresh via/api/auth/refresh. - Fetch references (users/statuses/stages/lead-sources) once and cache the UUIDs.
- Create entities in a logical order: client → contact → lead → deal.
- Use paged endpoints for reads with server-side filtering and sorting.
- For exports, use NDJSON endpoints and read the stream line by line.
- For bulk ingestion, use batch endpoints (
/api/leads/import/batch,/api/clients/import/batch) with rollback support. - Send marketing attribution via
POST /api/dashboard/marketing/eventswith a stableexternalId. - Never store or pass PII in marketing-event
metadata. - See detailed per-API docs:
site-leads-integration-guide.md.
Reactive CRM