API Reference
REST and ObjectQL endpoints, authentication, pagination, webhooks, and the platform SDKs.
API Reference
HotCRM exposes its full data plane through a REST + ObjectQL API. The same endpoints are used by the web UI, the mobile app, the AI Copilot, and any external integration.
For comprehensive endpoint-by-endpoint reference (auto-generated from the OpenAPI spec), see the API Explorer at
your-hotcrm-url/api/explorer.
Authentication
OAuth 2.0 (recommended for apps)
Standard authorisation code flow:
GET /oauth/authorize
?response_type=code
&client_id=YOUR_CLIENT_ID
&redirect_uri=https://your-app/callback
&scope=read:opportunity write:opportunityExchange the code for tokens:
POST /oauth/token
Content-Type: application/x-www-form-urlencoded
grant_type=authorization_code
&code=...
&client_id=YOUR_CLIENT_ID
&client_secret=YOUR_CLIENT_SECRET
&redirect_uri=https://your-app/callbackUse the access token in subsequent requests:
GET /api/v1/opportunities
Authorization: Bearer <access_token>Tokens last 1 hour; refresh with the refresh token.
Personal Access Tokens (for scripts)
For server-to-server scripts, generate a PAT in Settings → Developer → Personal Access Tokens. Use as a Bearer token.
⚠️ PATs inherit your user's permissions. Treat as secrets.
API keys (for integrations)
For named integrations (Stripe, DocuSign, your own):
- Setup → Integrations → API Keys → New.
- Pick scopes.
- Copy the key (shown once).
API keys can be scoped to specific objects + actions and have their own rate limits.
Object endpoints
Every object exposes a uniform set of endpoints. Examples for crm_opportunity:
| Method | Endpoint | Purpose |
|---|---|---|
GET | /api/v1/opportunities | List with filter / sort / paginate |
GET | /api/v1/opportunities/{id} | Single record by ID |
POST | /api/v1/opportunities | Create |
PATCH | /api/v1/opportunities/{id} | Partial update |
PUT | /api/v1/opportunities/{id} | Full replace |
DELETE | /api/v1/opportunities/{id} | Soft delete (recycle bin) |
POST | /api/v1/opportunities/{id}/restore | Restore from recycle bin |
POST | /api/v1/opportunities/bulk | Create / update in batches of up to 10,000 |
POST | /api/v1/opportunities/query | ObjectQL query |
Substitute the object name (snake_case plural for list) — accounts, contacts, leads, cases, contracts, products, quotes, campaigns, tasks, events, etc.
ObjectQL
For anything beyond simple field equality, use ObjectQL via POST /api/v1/{object}/query:
POST /api/v1/opportunities/query
{
"filters": [
["stage", "in", ["proposal", "negotiation"]],
["amount", ">", 100000],
["close_date", "between", "2026-01-01", "2026-03-31"]
],
"sort": [{ "field": "amount", "direction": "desc" }],
"limit": 200,
"cursor": "...",
"fields": ["id", "name", "amount", "stage", "account.name", "owner.name"],
"expand": ["crm_account", "owner", "crm_opportunity_line_item"]
}Supported operators
=, !=, >, >=, <, <=, in, not_in, between, contains, starts_with, ends_with, is_null, is_not_null, in_role_subordinates, in_group.
Pagination
Cursor-based. Each response includes next_cursor. Pass it back to fetch the next page.
{
"results": [...],
"next_cursor": "eyJpZCI6...",
"has_more": true
}Never use offset-based pagination for large datasets — it's O(n).
Joining related records
Use expand to inline lookup targets:
{
"fields": ["id", "name", "account.name", "account.industry"],
"expand": ["crm_account"]
}Multi-level joins (e.g., opportunity.account.parent_account) are supported up to 3 levels deep.
Aggregation
POST /api/v1/opportunities/aggregate
{
"filters": [["stage", "=", "closed_won"], ["close_date", "in_year", 2026]],
"group_by": ["owner", "month(close_date)"],
"metrics": [
{ "field": "amount", "op": "sum", "as": "total_bookings" },
{ "field": "amount", "op": "avg", "as": "avg_deal_size" },
{ "field": "id", "op": "count", "as": "deal_count" }
]
}Webhooks
Subscribe
POST /api/v1/webhooks
{
"name": "stripe-sync-on-contract-active",
"object": "crm_contract",
"events": ["updated"],
"filter": [["status", "=", "activated"]],
"url": "https://your-app/webhooks/contract-activated",
"secret": "shhh",
"auth": { "type": "hmac_sha256", "header": "X-Signature" }
}Payload
{
"event": "contract.updated",
"occurred_at": "2026-03-12T14:23:11Z",
"tenant_id": "ten_abc",
"record_id": "ctr_xyz",
"before": { ... },
"after": { ... },
"user_id": "usr_alex"
}Verify the X-Signature header before processing.
Event bus (streaming)
For high-volume integrations, subscribe to the event bus (Kafka / EventBridge / Pub/Sub) instead of HTTP webhooks:
import { EventBus } from '@hotcrm/sdk';
const bus = new EventBus({ token: process.env.HOTCRM_TOKEN });
bus.subscribe('opportunity.closed_won', async (event) => {
await provisionCustomer(event.record);
});SDKs
Official SDKs:
- TypeScript / Node —
@hotcrm/sdk - Python —
hotcrm-sdk - Go —
github.com/hotcrm/hotcrm-go
All SDKs:
- Handle auth, retries, back-off, pagination.
- Provide typed objects (generated from your tenant's schema).
- Stream events from the event bus.
import { Hotcrm } from '@hotcrm/sdk';
const crm = new Hotcrm({ token: process.env.HOTCRM_TOKEN });
const opps = await crm.opportunity.query({
filters: [['stage', '=', 'closed_won']],
limit: 200,
});
for await (const opp of crm.opportunity.queryAll({ filters: [...] })) {
console.log(opp.name);
}
await crm.opportunity.update('opp_123', { amount: 50000 });AI skill API
Invoke any AI skill the user has access to:
POST /api/v1/ai/skills/email_drafting/invoke
{
"input": {
"record_id": "opp_123",
"goal": "discovery follow-up after demo"
}
}Response includes the structured output + citations.
For streaming responses (typewriter UX):
POST /api/v1/ai/skills/{skill}/streamreturns server-sent events.
Rate limits
See Performance & Limits. Honour the Retry-After header on 429 responses.
Errors
Standard HTTP status codes. Body:
{
"error": {
"code": "validation_failed",
"message": "Amount is required when moving to Negotiation",
"field": "amount",
"request_id": "req_abc123"
}
}Include request_id when filing issues.
Versioning
- URL-versioned:
/api/v1/... - Breaking changes ship in
/api/v2/...—v1supported for 24 months afterv2GA. - Additive changes (new fields, new endpoints) ship in
v1without notice. - Deprecations appear in the
Sunsetheader at least 6 months before removal.
Sandbox & testing
The test mode API base URL is your-sandbox-url/api/v1/. Identical endpoints, isolated data.
For local development, the open-source dev runtime exposes the full API at localhost:3000/api/v1/.
Tips
- ✅ Use the SDK — handwriting HTTP gets tedious and error-prone.
- ✅ Always implement back-off — even if you think you're under the limit.
- ✅ Use cursor pagination, never offset.
- ✅ Prefer the event bus over polling.
- ✅ Verify webhook signatures — never trust the source IP alone.
- ✅ Test against a sandbox before production.