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

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:opportunity

Exchange 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/callback

Use 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):

  1. Setup → Integrations → API Keys → New.
  2. Pick scopes.
  3. 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:

MethodEndpointPurpose
GET/api/v1/opportunitiesList with filter / sort / paginate
GET/api/v1/opportunities/{id}Single record by ID
POST/api/v1/opportunitiesCreate
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}/restoreRestore from recycle bin
POST/api/v1/opportunities/bulkCreate / update in batches of up to 10,000
POST/api/v1/opportunities/queryObjectQL 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).

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
  • Pythonhotcrm-sdk
  • Gogithub.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}/stream

returns 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/...v1 supported for 24 months after v2 GA.
  • Additive changes (new fields, new endpoints) ship in v1 without notice.
  • Deprecations appear in the Sunset header 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.

On this page