tercela.API Reference

Introduction

The Tercela API is a JSON-over-HTTP REST API. Every tenant-owned resource is scoped to your active organization, and all requests share the same base URL. Send and receive JSON with Content-Type: application/json.

# Base URL
https://tercela.com/api/v1

# Health check (no auth)
curl https://tercela.com/healthz

Authentication

Register or sign in to get a token pair: a JWT access token (valid 4h) and an opaque refresh token (valid 7 days, single-use — it rotates on every refresh). Send the access token as a Bearer header. The access token also carries your active organization, so most endpoints need no extra tenant parameter.

MethodPathAuth
POST/identity/register
POST/identity/login
POST/identity/refresh
GET/identity/meBearer
# 1. Register (also creates your personal org)
curl -X POST https://tercela.com/api/v1/identity/register \
  -H "Content-Type: application/json" \
  -d '{"userName":"jane","email":"jane@acme.com","password":"Abcdef1!"}'

# 2. Login → tokens
curl -X POST https://tercela.com/api/v1/identity/login \
  -H "Content-Type: application/json" \
  -d '{"userName":"jane","password":"Abcdef1!"}'
# → { "userId": "...", "tokens": { "accessToken": "...", "refreshToken": "..." } }

# 3. Use the access token
curl https://tercela.com/api/v1/identity/me -H "Authorization: Bearer <accessToken>"

Organizations

Users are global and can belong to many organizations. Everyone gets a personal org at sign-up; create a team org to invite others. The active org travels in the access token — switching orgs (or creating one) returns a fresh token pair. Roles are owner / admin / member.

MethodPathAuth
GET/organizationsBearer
POST/organizationsBearer
POST/organizations/switchBearer
POST/organizations/invitationsowner/admin
POST/organizations/invitations/acceptBearer
# Create a team org (returns a new token pair for that org)
curl -X POST https://tercela.com/api/v1/organizations \
  -H "Authorization: Bearer <accessToken>" \
  -H "Content-Type: application/json" \
  -d '{"name":"ACME Corp"}'

# Switch back to another org you belong to
curl -X POST https://tercela.com/api/v1/organizations/switch \
  -H "Authorization: Bearer <accessToken>" \
  -d '{"orgId":"<org-id>"}'

Channels & Connections

A connection is a social account linked to your organization (WhatsApp, Instagram, X, TikTok). Access tokens are encrypted at rest and never returned. WhatsApp connects through Facebook Login (see below); the other platforms register a token directly for now. Sandbox mode simulates the platform APIs until the app reviews are approved.

MethodPathAuth
GET/channels/connectionsBearer + org
POST/channels/connectionsBearer + org
DELETE/channels/connections/{id}Bearer + org
POST/channels/messagesBearer + org
GET/channels/conversationsBearer + org
GET/channels/conversations/{id}/messagesBearer + org
GET/channels/conversations/unread-countBearer + org
GET/channels/analyticsBearer + org
# List the org's connections
curl https://tercela.com/api/v1/channels/connections \
  -H "Authorization: Bearer <accessToken>"

# Send a WhatsApp message (also recorded in the conversation thread)
curl -X POST https://tercela.com/api/v1/channels/messages \
  -H "Authorization: Bearer <accessToken>" \
  -H "Content-Type: application/json" \
  -d '{"connectionId":"<id>","to":"+5511999990000","text":"Hello from Tercela!"}'

# Inbox: list conversations, then read one thread
curl "https://tercela.com/api/v1/channels/conversations?platform=whatsapp" \
  -H "Authorization: Bearer <accessToken>"
curl https://tercela.com/api/v1/channels/conversations/<id>/messages \
  -H "Authorization: Bearer <accessToken>"

Connecting WhatsApp

WhatsApp uses Meta's Facebook Login for Business. The browser runs FB Login (SDK), the backend lists the WhatsApp Business Accounts and numbers the resulting user token can access, the user picks a number, and the short-lived token is exchanged for a long-lived one server-side (the app secret never reaches the browser). The app is then subscribed to the WABA for inbound webhooks.

MethodPathAuth
GET/channels/whatsapp/configBearer
POST/channels/whatsapp/accountsBearer + org
POST/channels/whatsapp/connectBearer + org
# After FB Login returns a user access token in the browser:
curl -X POST https://tercela.com/api/v1/channels/whatsapp/accounts \
  -H "Authorization: Bearer <accessToken>" \
  -d '{"accessToken":"<fb-user-token>"}'
# → { "accounts": [ { "wabaId": "...", "phoneNumbers": [ { "phoneNumberId": "...",
#      "displayPhoneNumber": "+1 555-167-2101", "verifiedName": "..." } ] } ] }

# Connect the picked number
curl -X POST https://tercela.com/api/v1/channels/whatsapp/connect \
  -H "Authorization: Bearer <accessToken>" \
  -H "Content-Type: application/json" \
  -d '{"accessToken":"<fb-user-token>","wabaId":"...","phoneNumberId":"...",
       "displayPhoneNumber":"+1 555-167-2101","verifiedName":"..."}'

Posts (publishing)

Publishing is asynchronous. A post fans out to one target per selected connection; a background worker delivers each target through the platform adapter, retries with backoff, and rolls the post status up to published / partial / failed. Poll the post to watch per-platform delivery.

MethodPathAuth
POST/postsBearer + org
GET/postsBearer + org
GET/posts/{id}Bearer + org
# Create a post targeting two connections
curl -X POST https://tercela.com/api/v1/posts \
  -H "Authorization: Bearer <accessToken>" \
  -H "Content-Type: application/json" \
  -d '{
    "body": "Launching our new product! 🚀",
    "media": [{"type":"image","url":"https://cdn.example.com/img.jpg"}],
    "targets": [{"connectionId":"<ig-conn>"}, {"connectionId":"<x-conn>"}]
  }'

# Poll delivery status
curl https://tercela.com/api/v1/posts/<post-id> -H "Authorization: Bearer <accessToken>"

Webhooks

Register endpoints and Tercela pushes signed event envelopes to your CRM or any system: message.received, message.sent, message.delivered, message.read, message.failed, reaction.received, conversation.started, connection.connected and connection.disconnected. Each POST carries X-Tercela-Event-Id and X-Tercela-Signature (hex HMAC-SHA256 of the raw body with your secret). Answer any 2xx within 5 seconds; failures are retried 7 times with exponential backoff (10^(n-1) seconds, capped at 24h). Delivery is at-least-once — dedupe by event id. After 10 consecutive failures the webhook is disabled automatically; re-enable it after fixing your endpoint.

MethodPathAuth
GET/webhooksBearer + org
POST/webhooksBearer + org
PUT/webhooks/{id}Bearer + org
DELETE/webhooks/{id}Bearer + org
POST/webhooks/{id}/testBearer + org
GET/webhooks/{id}/deliveriesBearer + org
# Create a webhook subscription
curl -X POST https://tercela.com/api/v1/webhooks \
  -H "Authorization: Bearer <accessToken>" \
  -H "Content-Type: application/json" \
  -d '{"name":"My CRM","url":"https://example.com/hooks/messaging",
       "secret":"whsec_...","events":["message.received","message.sent"]}'

# Envelope delivered to your endpoint:
# POST https://example.com/hooks/messaging
# X-Tercela-Event-Id: 4fc4cacf-...
# X-Tercela-Signature: d5c35bdb... (hex HMAC-SHA256 of the raw body)
{ "id": "4fc4cacf-...", "event": "message.received",
  "timestamp": "2026-07-04T16:46:50Z", "platform": "whatsapp",
  "message": { "id": "...", "type": "text", "body": "Hello!", "direction": "in" },
  "conversation": { "id": "...", "contactName": "...", "contactPhone": "..." },
  "connection": { "id": "..." } }

# Verify the signature (Node.js)
const crypto = require('crypto')
const expected = crypto.createHmac('sha256', secret).update(rawBody).digest('hex')
const valid = crypto.timingSafeEqual(Buffer.from(expected),
  Buffer.from(req.headers['x-tercela-signature']))

Platform webhooks (inbound)

Meta calls Tercela's webhook endpoint to verify the subscription (GET challenge) and to deliver inbound WhatsApp events (POST). Events are stored raw and processed asynchronously, so the endpoint always answers fast. These are platform-internal — your systems consume the outbound Webhooks above.

MethodPathAuth
GET/webhooks/whatsapp— (Meta)
POST/webhooks/whatsapp— (Meta)

Partner console (white-label)

For white-label partners: accounts holding the tenant-admin role administer their own workspace — every client organization with usage, the user directory, custom domains and your own Meta app — scoped to the tenant of the host you call. The WhatsApp provider is decided by the platform per workspace: either the platform's general tech-provider app (default; the Meta app settings are locked) or your own Meta app (required then — connections stay disabled until you configure it; Facebook Login runs directly on your domain and webhooks are validated against your app secret). Grant the role to another user of your workspace with the users endpoints (you cannot revoke your own). A granted role takes effect on the next sign-in. Tenant-admins see client usage, never client data.

MethodPathAuth
GET/tenant-admin/overviewtenant-admin
GET/tenant-admin/orgstenant-admin
GET/tenant-admin/userstenant-admin
PUT/tenant-admin/users/{userId}/admintenant-admin
DELETE/tenant-admin/users/{userId}/admintenant-admin
GET/tenant-admin/channels/whatsapp-apptenant-admin
PUT/tenant-admin/channels/whatsapp-apptenant-admin
DELETE/tenant-admin/channels/whatsapp-apptenant-admin
POST/tenant-admin/channels/whatsapp-app/validatetenant-admin
GET/tenants/domainstenant-admin
POST/tenants/domainstenant-admin
POST/tenants/domains/{id}/verifytenant-admin
DELETE/tenants/domains/{id}tenant-admin
# Workspace-wide usage (call it on YOUR workspace host)
curl https://tercela.com/api/v1/tenant-admin/overview \
  -H "Authorization: Bearer <accessToken>"

# Your client organizations, with usage
curl "https://tercela.com/api/v1/tenant-admin/orgs?limit=20" \
  -H "Authorization: Bearer <accessToken>"

# Promote a teammate to workspace admin
curl -X PUT https://tercela.com/api/v1/tenant-admin/users/<userId>/admin \
  -H "Authorization: Bearer <accessToken>"

Errors

Errors return the matching HTTP status and a JSON body { "error": "message" }. A 401 means the access token is missing or expired — refresh it and retry. A 403 on a tenant route means no active organization or an insufficient role.

{ "error": "invalid credentials" }        # 401
{ "error": "insufficient organization role" }  # 403
{ "error": "post not found" }             # 404