CalcHub REST API

Public API for integrating CalcHub with your own tools, dashboards and automations. Base URL: https://calchub.echo-digital.es

Authentication

Every request to /api/v1/* requires a personal API key. Generate one at Dashboard → API Keys. The full token is shown once — copy it straight away. Pass it as a standard Bearer token:

Authorization: Bearer calk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

Keep keys out of client-side JavaScript and mobile apps. Revoke a compromised key from the dashboard; any integration using it will stop working within seconds.

Rate limits

60 requests per minute per API key. Exceeded requests get HTTP 429. If you need higher throughput, open multiple keys per integration and spread the traffic.

Errors

All errors return JSON of the shape { "error": "..." } with an appropriate HTTP status:

  • 400 — bad request payload
  • 401 — missing / invalid / revoked token
  • 403 — resource not owned by the authenticated user
  • 404 — resource not found
  • 429 — rate limit or abuse limit exceeded
  • 500 — server-side failure, report to support

Pagination

List endpoints use keyset cursors. The response shape is:

{
  "items": [ ... ],
  "nextCursor": "cmo...",   // pass as ?cursor= on the next call
  "hasMore": true
}

?take= defaults to 50, max 200.

Calculators

GET/api/v1/calculators
List calculators

All calculators assigned to the authenticated user. Supports cursor pagination (?take=, ?cursor=).

Example
curl -H "Authorization: Bearer calk_YOUR_TOKEN" "https://calchub.echo-digital.es/api/v1/calculators?take=50"
Response
{
  "items": [
    { "id": "cmo...", "name": "Recuperator Calculator", "slug": "recuperator", "embedToken": "cmo...", "allowedDomains": ["mycond.eu"], "isActive": true, "createdAt": "2026-04-21T..." }
  ],
  "nextCursor": null,
  "hasMore": false
}
GET/api/v1/calculators/:id
Get a calculator

Full calculator including definition JSON, linked bot id, allowed domains and embed token.

Example
curl -H "Authorization: Bearer calk_YOUR_TOKEN" "https://calchub.echo-digital.es/api/v1/calculators/cmo..."
POST/api/v1/calculators
Create a calculator

Creates a calculator from a CalculatorDefinition JSON and auto-assigns it to you, returning the embed token.

Example
curl -X POST -H "Authorization: Bearer calk_YOUR_TOKEN" -H "Content-Type: application/json" \
  -d '{
    "name": "Recuperator savings",
    "slug": "recuperator-savings",
    "category": "HVAC",
    "definition": { "elements": [ ... ], "settings": { ... } },
    "allowedDomains": ["mycond.eu"]
  }' "https://calchub.echo-digital.es/api/v1/calculators"
Response
HTTP 201
{ "id": "cmo...", "name": "Recuperator savings", "slug": "recuperator-savings", "embedToken": "cmo...", "allowedDomains": ["mycond.eu"] }
POST/api/v1/calculators/:id
Update a calculator

Use PUT. Patches any of: name, description, category, isActive, botId, definition.

Example
curl -X PUT -H "Authorization: Bearer calk_YOUR_TOKEN" -H "Content-Type: application/json" \
  -d '{ "isActive": false }' "https://calchub.echo-digital.es/api/v1/calculators/cmo..."
DELETE/api/v1/calculators/:id
Delete a calculator

Cascades: removes the UserCalculator assignment and the Calculator itself. Leads and stats are preserved.

Example
curl -X DELETE -H "Authorization: Bearer calk_YOUR_TOKEN" "https://calchub.echo-digital.es/api/v1/calculators/cmo..."
POST/api/v1/calculators/:id/domains
Update allowed domains (PUT)

Replaces the allowedDomains whitelist for this calculator. Empty array = any origin. Subdomain match ('mycond.eu' allows 'shop.mycond.eu').

Example
curl -X PUT -H "Authorization: Bearer calk_YOUR_TOKEN" -H "Content-Type: application/json" \
  -d '{ "allowedDomains": ["mycond.eu", "partner-site.com"] }' \
  "https://calchub.echo-digital.es/api/v1/calculators/cmo.../domains"
POST/api/v1/calculators/generate
AI-generate a calculator draft

Takes a natural-language prompt, runs Gemini (your saved key or the server fallback) and returns a CalculatorDefinition + rendered HTML. Does NOT persist — pipe into POST /v1/calculators to save.

Example
curl -X POST -H "Authorization: Bearer calk_YOUR_TOKEN" -H "Content-Type: application/json" \
  -d '{ "prompt": "Calculator for home heating savings with electricity price in UAH" }' \
  "https://calchub.echo-digital.es/api/v1/calculators/generate"
Response
{ "definition": { "elements": [...], "settings": {...} }, "html": "<form>...</form>" }

Leads

GET/api/v1/leads
List leads

Newest first. Filters: ?calculatorId=, ?synced=true|false. Cursor pagination via ?cursor=.

Example
curl -H "Authorization: Bearer calk_YOUR_TOKEN" "https://calchub.echo-digital.es/api/v1/leads?take=50&synced=false"
Response
{
  "items": [
    { "id": "cmo...", "contactName": "John", "email": "j@ex.com", "phone": "+380...", "calculatorData": { ... }, "odooSyncStatus": { "synced": true, "recordIds": [{ "model": "res.partner", "id": 79647, "action": "linked" }], "attemptedAt": "2026-04-21T..." }, "createdAt": "..." }
  ],
  "nextCursor": "...",
  "hasMore": true
}
GET/api/v1/leads/:id
Get a lead

Returns the full Lead row including calculatorData and odooSyncStatus.

Example
curl -H "Authorization: Bearer calk_YOUR_TOKEN" "https://calchub.echo-digital.es/api/v1/leads/cmo..."
POST/api/v1/leads
Create a lead

Server-to-server lead creation. Skips honeypot / time-gate / Turnstile (they're only meaningful for browser submissions) but still enforces your abuse limits (per-phone/email/day, E.164).

Example
curl -X POST -H "Authorization: Bearer calk_YOUR_TOKEN" -H "Content-Type: application/json" \
  -d '{
    "calculatorId": "cmo...",
    "contactName": "John",
    "email": "j@ex.com",
    "phone": "+380...",
    "preferredChannel": "email",
    "data": { "area": 120, "city": "Kyiv" }
  }' \
  "https://calchub.echo-digital.es/api/v1/leads"
Response
HTTP 201
{ "id": "cmo...", "userId": "...", "calculatorId": "cmo...", ... }
POST/api/v1/leads/:id/sync
Retry Odoo sync

Forces an immediate (re-)sync of this lead to Odoo. Useful after fixing a broken mapping or Odoo credentials.

Example
curl -X POST -H "Authorization: Bearer calk_YOUR_TOKEN" "https://calchub.echo-digital.es/api/v1/leads/cmo.../sync"
POST/api/v1/leads/retry-failed
Retry every failed lead

Bulk re-sync for up to 500 leads where the last Odoo push failed. Returns {attempted, ok, fail}.

Example
curl -X POST -H "Authorization: Bearer calk_YOUR_TOKEN" "https://calchub.echo-digital.es/api/v1/leads/retry-failed"
DELETE/api/v1/leads/:id
Delete a lead

Removes the lead. Conversations referencing it (via leadId) stay but the relation becomes dangling — only use for GDPR or test-data cleanup.

Example
curl -X DELETE -H "Authorization: Bearer calk_YOUR_TOKEN" "https://calchub.echo-digital.es/api/v1/leads/cmo..."
GET/api/v1/leads?format=csv
Export leads as CSV

Returns text/csv with all matching leads (up to 10 000) for the given filters. Headers: id, createdAt, calculatorId, contactName, email, phone, telegramNick, preferredChannel, calculatorData, odooSynced.

Example
curl -H "Authorization: Bearer calk_YOUR_TOKEN" "https://calchub.echo-digital.es/api/v1/leads?format=csv" -o leads.csv

Conversations

GET/api/v1/conversations
List conversations

Filters: ?channel=telegram|email|whatsapp, ?status=active|escalated|takeover|closed, ?botId=. Cursor pagination.

Example
curl -H "Authorization: Bearer calk_YOUR_TOKEN" "https://calchub.echo-digital.es/api/v1/conversations?status=escalated"
GET/api/v1/conversations/:id
Get a conversation with messages

Returns the lead, the linked bot, summary fields and the latest N messages (default 50). Load older with ?before=<messageId>.

Example
curl -H "Authorization: Bearer calk_YOUR_TOKEN" "https://calchub.echo-digital.es/api/v1/conversations/cmo..."
POST/api/v1/conversations/:id/messages
Send takeover reply

Human operator sends a message into the conversation through the bot's channel. Sets status=takeover so the AI stops auto-replying.

Example
curl -X POST -H "Authorization: Bearer calk_YOUR_TOKEN" -H "Content-Type: application/json" \
  -d '{ "content": "Hi, this is the manager!" }' \
  "https://calchub.echo-digital.es/api/v1/conversations/cmo.../messages"
Response
HTTP 201
{ "id": "cmo...", "role": "takeover", "content": "...", "createdAt": "..." }
POST/api/v1/conversations/:id
Change status (PUT)

Transition status between active / closed / takeover / escalated. Takeover pauses AI auto-reply.

Example
curl -X PUT -H "Authorization: Bearer calk_YOUR_TOKEN" -H "Content-Type: application/json" \
  -d '{ "status": "closed" }' \
  "https://calchub.echo-digital.es/api/v1/conversations/cmo..."
POST/api/v1/conversations/:id/summary
Generate AI summary

Runs the bot's AI over the conversation and saves a short Request / Key facts / Status / Next step summary on the Conversation.

Example
curl -X POST -H "Authorization: Bearer calk_YOUR_TOKEN" "https://calchub.echo-digital.es/api/v1/conversations/cmo.../summary"
Response
{ "summary": "Request: ...\nKey facts:\n- ...", "summaryAt": "...", "summaryMessageCount": 18 }

Bots

GET/api/v1/bots
List bots

All bots you own, with channel config summaries and conversation counts.

Example
curl -H "Authorization: Bearer calk_YOUR_TOKEN" "https://calchub.echo-digital.es/api/v1/bots"
GET/api/v1/bots/:id
Get a bot

Full bot settings including prompt, model, reply timing, working hours.

Example
curl -H "Authorization: Bearer calk_YOUR_TOKEN" "https://calchub.echo-digital.es/api/v1/bots/cmo..."
POST/api/v1/bots
Create a bot

Enforces the same rules as the dashboard: name + prompt required, required AI key for the chosen model, SMTP fromEmail must match SMTP user domain, Telegram webhook auto-registered with a per-bot secret.

Example
curl -X POST -H "Authorization: Bearer calk_YOUR_TOKEN" -H "Content-Type: application/json" \
  -d '{
    "name": "Sales bot",
    "prompt": "You are a friendly sales assistant for ...",
    "model": "models/gemini-flash-latest",
    "responseLanguage": "Ukrainian",
    "replyDelayMin": 10,
    "replyDelayMax": 15,
    "typingIndicator": true
  }' "https://calchub.echo-digital.es/api/v1/bots"
POST/api/v1/bots/:id
Update a bot (PUT)

Patch any subset of bot fields. Changing the model across providers still requires the matching API key.

Example
curl -X PUT -H "Authorization: Bearer calk_YOUR_TOKEN" -H "Content-Type: application/json" \
  -d '{ "isActive": false }' "https://calchub.echo-digital.es/api/v1/bots/cmo..."
DELETE/api/v1/bots/:id
Delete a bot

Unlinks calculators from the bot and removes the bot. Conversations and leads stay.

Example
curl -X DELETE -H "Authorization: Bearer calk_YOUR_TOKEN" "https://calchub.echo-digital.es/api/v1/bots/cmo..."

Stats

GET/api/v1/stats
Overall activity

Totals plus daily impressions / interactions / submissions for the last N days (?days=30, max 365). Aggregates across every calculator you own.

Example
curl -H "Authorization: Bearer calk_YOUR_TOKEN" "https://calchub.echo-digital.es/api/v1/stats?days=30"
Response
{
  "totals": { "leads": 128, "conversations": 94, "impressions": 5210, "interactions": 1840, "submissions": 132 },
  "daily": [
    { "date": "2026-04-21", "impressions": 210, "interactions": 74, "submissions": 6 }
  ]
}
GET/api/v1/stats/calculators/:id
Per-calculator activity

Daily impressions / interactions / submissions for one of your calculators, last N days (?days=30, max 365).

Example
curl -H "Authorization: Bearer calk_YOUR_TOKEN" "https://calchub.echo-digital.es/api/v1/stats/calculators/cmo...?days=90"

Campaigns

GET/api/v1/campaigns
List campaigns

All active / paused campaigns you own. Archived campaigns are excluded.

Example
curl -H "Authorization: Bearer calk_YOUR_TOKEN" "https://calchub.echo-digital.es/api/v1/campaigns"
GET/api/v1/campaigns/:id
Get a campaign

Full campaign config + the 20 most recent runs with their counters.

Example
curl -H "Authorization: Bearer calk_YOUR_TOKEN" "https://calchub.echo-digital.es/api/v1/campaigns/cmo..."
POST/api/v1/campaigns
Create a campaign

botId must belong to you. audience filter supports calculatorIds, channels, conversationStatuses, inactivityDays, createdAfter, createdBefore. runIntervalDays=null for manual-only; any positive number enables the scheduler.

Example
curl -X POST -H "Authorization: Bearer calk_YOUR_TOKEN" -H "Content-Type: application/json" \
  -d '{
    "name": "Weekly nudge",
    "botId": "cmo...",
    "reminderPrompt": "Remind the customer politely to book a consultation.",
    "audience": { "channels": ["whatsapp"], "inactivityDays": 7 },
    "runIntervalDays": 7,
    "msgPerMinute": 20,
    "maxPerRun": 500
  }' "https://calchub.echo-digital.es/api/v1/campaigns"
Response
HTTP 201
{ "id": "cmo...", "status": "active", ... }
POST/api/v1/campaigns/:id
Update a campaign (PUT)

Patch any of name, reminderPrompt, audience, runIntervalDays, msgPerMinute, maxPerRun. 409 if a run is in progress — pause first.

Example
curl -X PUT -H "Authorization: Bearer calk_YOUR_TOKEN" -H "Content-Type: application/json" \
  -d '{ "runIntervalDays": 14 }' "https://calchub.echo-digital.es/api/v1/campaigns/cmo..."
DELETE/api/v1/campaigns/:id
Archive a campaign

Soft-delete: campaign stops running but history (runs / messages) is preserved for audit.

Example
curl -X DELETE -H "Authorization: Bearer calk_YOUR_TOKEN" "https://calchub.echo-digital.es/api/v1/campaigns/cmo..."
POST/api/v1/campaigns/:id/run
Run manually

Triggers a run immediately. Returns 202 Accepted — the worker finishes in the background. Same atomic claim as the scheduler, so a parallel scheduled run wins the race and the API returns 409.

Example
curl -X POST -H "Authorization: Bearer calk_YOUR_TOKEN" "https://calchub.echo-digital.es/api/v1/campaigns/cmo.../run"
POST/api/v1/campaigns/:id/pause
Pause scheduling

Scheduler stops triggering this campaign. A run already in progress is NOT aborted — wait for it to finish, then pause takes effect.

Example
curl -X POST -H "Authorization: Bearer calk_YOUR_TOKEN" "https://calchub.echo-digital.es/api/v1/campaigns/cmo.../pause"
POST/api/v1/campaigns/:id/resume
Resume scheduling

Flips status back from paused to active.

Example
curl -X POST -H "Authorization: Bearer calk_YOUR_TOKEN" "https://calchub.echo-digital.es/api/v1/campaigns/cmo.../resume"
POST/api/v1/campaigns/audience/preview
Preview audience

Given a draft filter, returns count + 10-row sample without persisting. Use before saving so you can see how many leads the campaign would touch.

Example
curl -X POST -H "Authorization: Bearer calk_YOUR_TOKEN" -H "Content-Type: application/json" \
  -d '{ "botId": "cmo...", "audience": { "inactivityDays": 7 } }' \
  "https://calchub.echo-digital.es/api/v1/campaigns/audience/preview"
Response
{ "count": 47, "sample": [ { "id": "cmo...", "contactName": "Bebra", "email": "..." }, ... ] }
GET/api/v1/campaigns/:id/runs
Run history

All runs of this campaign, newest first. Cursor pagination.

Example
curl -H "Authorization: Bearer calk_YOUR_TOKEN" "https://calchub.echo-digital.es/api/v1/campaigns/cmo.../runs"
GET/api/v1/campaigns/:id/runs/:runId
Run details

Run metadata + its messages (pending / sent / failed / skipped with the AI-generated content actually delivered).

Example
curl -H "Authorization: Bearer calk_YOUR_TOKEN" "https://calchub.echo-digital.es/api/v1/campaigns/cmo.../runs/cmo..."

Monitoring

GET/api/v1/errors
List bot errors

Recent BotError entries scoped to your bots. Filters: ?botId=, ?type=, ?conversationId=, ?days= (1-90). Cursor pagination.

Example
curl -H "Authorization: Bearer calk_YOUR_TOKEN" "https://calchub.echo-digital.es/api/v1/errors?days=7&type=ai"
GET/api/v1/ai-models
List usable AI models

Returns the Gemini and Anthropic models your saved API keys can actually call. Empty array for a provider = no key saved.

Example
curl -H "Authorization: Bearer calk_YOUR_TOKEN" "https://calchub.echo-digital.es/api/v1/ai-models"
POST/api/v1/ai-models/test
Test Gemini models

Pings every text-gen Gemini model your key sees and reports which actually respond (free-tier limits often reject some). Does NOT probe Claude — Anthropic has no cheap health ping.

Example
curl -X POST -H "Authorization: Bearer calk_YOUR_TOKEN" "https://calchub.echo-digital.es/api/v1/ai-models/test"
Response
{ "results": [ { "name": "models/gemini-2.5-flash", "displayName": "Gemini 2.5 Flash", "ok": true, "status": 200 }, ... ] }

Test connections

POST/api/v1/test/telegram
Verify a Telegram bot token

Calls Telegram's getMe so you can validate a token before saving. Body: { token }.

Example
curl -X POST -H "Authorization: Bearer calk_YOUR_TOKEN" -H "Content-Type: application/json" \
  -d '{ "token": "123456:ABC..." }' "https://calchub.echo-digital.es/api/v1/test/telegram"
POST/api/v1/test/smtp
Verify SMTP credentials

Attempts to open an SMTP connection and authenticate. Body: full SmtpConfig (host, port, secure, user, pass, fromEmail, fromName).

Example
curl -X POST -H "Authorization: Bearer calk_YOUR_TOKEN" -H "Content-Type: application/json" \
  -d '{ "host": "smtp.gmail.com", "port": 587, "user": "you@gmail.com", "pass": "..." }' \
  "https://calchub.echo-digital.es/api/v1/test/smtp"
POST/api/v1/test/whatsapp
Verify WhatsApp config

Pings the provider (Wasender or Meta) to confirm credentials and return the connected phone. Body: WhatsAppConfig.

Example
curl -X POST -H "Authorization: Bearer calk_YOUR_TOKEN" -H "Content-Type: application/json" \
  -d '{ "provider": "wassenger", "wassengerToken": "..." }' \
  "https://calchub.echo-digital.es/api/v1/test/whatsapp"
POST/api/v1/test/odoo
Verify Odoo credentials

Dry-run authenticate against Odoo's JSON-RPC. Returns { ok, uid, error }. Body: { url, db, username, apiKey }.

Example
curl -X POST -H "Authorization: Bearer calk_YOUR_TOKEN" -H "Content-Type: application/json" \
  -d '{ "url": "https://stage.example.com", "db": "erp_stage", "username": "you@ex.com", "apiKey": "..." }' \
  "https://calchub.echo-digital.es/api/v1/test/odoo"

Settings

GET/api/v1/profile
Get your profile

Basic account info (id, email, name, role).

Example
curl -H "Authorization: Bearer calk_YOUR_TOKEN" "https://calchub.echo-digital.es/api/v1/profile"
POST/api/v1/profile
Update profile (PUT)

Only updatable field via the API is `name`. Password resets and email changes stay dashboard-only.

Example
curl -X PUT -H "Authorization: Bearer calk_YOUR_TOKEN" -H "Content-Type: application/json" \
  -d '{ "name": "Luka H." }' "https://calchub.echo-digital.es/api/v1/profile"
GET/api/v1/settings/abuse-limits
Get abuse limits

Current per-phone/email/IP daily caps + E.164 enforcement toggle. Also returns the platform defaults.

Example
curl -H "Authorization: Bearer calk_YOUR_TOKEN" "https://calchub.echo-digital.es/api/v1/settings/abuse-limits"
POST/api/v1/settings/abuse-limits
Update abuse limits (PUT)

Accepts phonePerDay, emailPerDay, ipPerHour, requireE164. Server clamps numerical values (0..100 / 0..1000). 0 disables a check.

Example
curl -X PUT -H "Authorization: Bearer calk_YOUR_TOKEN" -H "Content-Type: application/json" \
  -d '{ "phonePerDay": 5, "emailPerDay": 5, "ipPerHour": 20, "requireE164": true }' \
  "https://calchub.echo-digital.es/api/v1/settings/abuse-limits"
GET/api/v1/settings/odoo
Odoo integration status

Connection metadata (url/db/username/key preview) plus current log-note + escalation notification settings. Never returns the raw Odoo API key.

Example
curl -H "Authorization: Bearer calk_YOUR_TOKEN" "https://calchub.echo-digital.es/api/v1/settings/odoo"
POST/api/v1/settings/odoo
Patch Odoo settings (PATCH)

Patch log-note template and escalation settings (enable flag, template, partnerIds to @-mention). Reconnecting Odoo (url/db/user/apiKey) stays dashboard-only.

Example
curl -X PATCH -H "Authorization: Bearer calk_YOUR_TOKEN" -H "Content-Type: application/json" \
  -d '{ "escalationNotifyEnabled": true, "escalationNotifyPartnerIds": [79647] }' \
  "https://calchub.echo-digital.es/api/v1/settings/odoo"
GET/api/v1/settings/odoo/users
List Odoo users

Active non-share Odoo users you can @-mention on escalation. Use the returned partnerId values in escalationNotifyPartnerIds above.

Example
curl -H "Authorization: Bearer calk_YOUR_TOKEN" "https://calchub.echo-digital.es/api/v1/settings/odoo/users"
GET/api/v1/settings/odoo/models
List Odoo models

All ir.model entries visible to your Odoo user. Cache 10 min.

Example
curl -H "Authorization: Bearer calk_YOUR_TOKEN" "https://calchub.echo-digital.es/api/v1/settings/odoo/models"
GET/api/v1/settings/odoo/fields?model=res.partner
List fields for an Odoo model

For building mapping UI. ?model= is the technical Odoo name (e.g. res.partner, crm.lead).

Example
curl -H "Authorization: Bearer calk_YOUR_TOKEN" "https://calchub.echo-digital.es/api/v1/settings/odoo/fields?model=res.partner"
GET/api/v1/settings/odoo/auto-sync
Get auto-sync interval

Returns intervalMinutes (0 = off, 15, 60, 360) and lastAutoSyncAt.

Example
curl -H "Authorization: Bearer calk_YOUR_TOKEN" "https://calchub.echo-digital.es/api/v1/settings/odoo/auto-sync"
POST/api/v1/settings/odoo/auto-sync
Set auto-sync interval (PUT)

Allowed values: 0, 15, 60, 360 (minutes). A background worker retries failed leads at this cadence.

Example
curl -X PUT -H "Authorization: Bearer calk_YOUR_TOKEN" -H "Content-Type: application/json" \
  -d '{ "intervalMinutes": 60 }' "https://calchub.echo-digital.es/api/v1/settings/odoo/auto-sync"
DELETE/api/v1/settings/odoo
Disconnect Odoo

Wipes saved Odoo credentials and clears the in-memory schema cache. Bots keep running, but future leads won't sync.

Example
curl -X DELETE -H "Authorization: Bearer calk_YOUR_TOKEN" "https://calchub.echo-digital.es/api/v1/settings/odoo"
DELETE/api/v1/settings/odoo/cache
Reset Odoo schema cache

Drops the cached models/fields (10 min TTL). Use after someone changes the Odoo schema and you want CalcHub to pick up new fields immediately.

Example
curl -X DELETE -H "Authorization: Bearer calk_YOUR_TOKEN" "https://calchub.echo-digital.es/api/v1/settings/odoo/cache"