Schemas
WhatsappHealthBlocker
entity_typePHONE_NUMBER, WABA, BUSINESS, APP or MESSAGE_TEMPLATE.
entity_idcan_send_messageThis entity's own verdict.
error_codedescriptionsolutionWhatsappAccountHealth
account_iddisplay_namephone_number_iddisplay_phone_numberverified_namequality_ratingmessaging_limit_tierRaw Meta tier code, read from the phone number's whatsapp_business_manager_messaging_limit field. Meta deprecated messaging_limit_tier on its side; this property keeps the name because it is this API's published contract.
messaging_limit_per_24hThe tier resolved to unique recipients reachable outside a customer service window per rolling 24 hours. Set per business portfolio and shared by every phone number in it. Null for TIER_UNLIMITED and for tiers Meta did not report - read it together with messaging_limit_tier to tell the two apart.
throughput_leveloperational_statusname_statuscan_send_messageseveritychecked_atLoginRequest
emailpasswordtotp_codeTOTP authenticator code, when completing a TOTP-2FA login.
email_codeThe emailed OTP, when completing an email-2FA login.
request_email_codeWhen true, ask the server to email an OTP instead of verifying a code on this request.
turnstile_tokenCloudflare Turnstile token, when the CAPTCHA widget is enabled.
remember_meWhen true, issue a longer-lived "remember me" session (7 days, cookie Max-Age and JWT exp) instead of the default TTL. Only takes effect after 2FA succeeds; ignored on 2FA-challenge responses.
org_idOptional workspace hint: which organization to look the email up in first. Without it the server searches every tenant it has loaded and takes the first email match, which misses a workspace provisioned seconds ago on another replica and is ambiguous when the same email exists in several workspaces. Sent by the post-registration sign-in flow. Never bypasses the password or 2FA checks.
LoginResponse
tokenJWT token
AgentPublic
idemailfull_nameroleis_onlineSsoOptions
google_enabledTrue when platform-level Google SSO login is configured.
SsoDiscoverRequest
emailturnstile_tokenCloudflare Turnstile token, when the CAPTCHA widget is enabled.
SsoDiscoverResponse
sso_availableTrue when an enabled company SSO provider accepts the email's domain.
start_urlRelative URL that begins the company SSO flow (carries a single-use ticket). Null when no provider matches.
PasskeyChallenge
challenge_idServer-side ceremony id; echo it back on the matching verify call.
optionsWebAuthn options to pass to the browser (navigator.credentials.create for registration, navigator.credentials.get for login).
PasskeyPublic
idcreated_atdevice_labelFriendly device name shown in the passkey list.
last_used_atSsoProviderPublic
idprovider_nameissuer_urlclient_idhas_client_secretTrue when a client secret is stored. The secret itself is never returned.
allowed_email_domainsenabledrequire_email_verifiedcreated_atupdated_atCreateSsoProviderRequest
provider_nameissuer_urlOIDC issuer base URL (must serve an OIDC discovery document).
client_idclient_secretStored encrypted at rest; never returned by the API.
allowed_email_domainsenabledrequire_email_verifiedUpdateSsoProviderRequest
provider_nameissuer_urlclient_idclient_secretallowed_email_domainsenabledrequire_email_verifiedConversationStatus
The complete set of conversation states. Mirrors ConversationStatus in crates/omni-common/src/models/conversation.rs.
pending and expired were removed by migrations/20260326000039_simplify_conversation_statuses.sql (pending → open, expired → resolved) and snoozed was added by migrations/20260519000012_conversation_snooze.sql. Passing a removed value as a status filter is rejected with 400 before the handler runs, because the query struct deserializes straight into this enum.
A snoozed conversation is hidden from the inbox until snoozed_until passes, at which point it reopens automatically.
ConversationWithContact
idcontact_idstatusThe complete set of conversation states. Mirrors ConversationStatus in crates/omni-common/src/models/conversation.rs.
pending and expired were removed by migrations/20260326000039_simplify_conversation_statuses.sql (pending → open, expired → resolved) and snoozed was added by migrations/20260519000012_conversation_snooze.sql. Passing a removed value as a status filter is rejected with 400 before the handler runs, because the query struct deserializes straight into this enum.
A snoozed conversation is hidden from the inbox until snoozed_until passes, at which point it reopens automatically.
last_message_atunread_countcreated_atupdated_atcontact_channelwhatsapp, instagram, or email
assigned_agent_idassigned_agent_nameFull name of the assigned agent (null when unassigned)
contact_namecontact_phoneAll co-assignees (human agents and/or AI agent) attached to this conversation via the conversation_assignees junction, ordered by assignment time. assigned_agent_id remains the denormalized primary.
Assignee
kindDiscriminates human vs AI assignee
agent_idSet when this assignee is a human agent
ai_agent_idSet when this assignee is an AI agent
nameDisplay name of the human or AI agent
AddAssigneeRequest
agent_idHuman agent to add as co-assignee
ai_agent_idAI agent to hand the conversation off to
UpdateStatusRequest
statusThe complete set of conversation states. Mirrors ConversationStatus in crates/omni-common/src/models/conversation.rs.
pending and expired were removed by migrations/20260326000039_simplify_conversation_statuses.sql (pending → open, expired → resolved) and snoozed was added by migrations/20260519000012_conversation_snooze.sql. Passing a removed value as a status filter is rejected with 400 before the handler runs, because the query struct deserializes straight into this enum.
A snoozed conversation is hidden from the inbox until snoozed_until passes, at which point it reopens automatically.
MessageType
Mirrors MessageType in crates/omni-common/src/models/message.rs. contacts (a shared contact card) and postback (a tapped button) are emitted by the channel engines; unknown covers anything the parsers did not recognise. flow is an outbound WhatsApp Flow send (content follows FlowSendContent, requires wa_flows.send); flow_response is the matching inbound completion and is never sent by a caller — its content carries a masked summary only, never the customer's answers (see WhatsAppFlowCompletedEvent and GET /api/wa-flow-responses/{session_id} for the real values).
MessageReaction
emojiagent_idagent_nameDenormalized display name, for tooltip rendering
created_atMessage
conversation_iddirectiontypeMirrors MessageType in crates/omni-common/src/models/message.rs. contacts (a shared contact card) and postback (a tapped button) are emitted by the channel engines; unknown covers anything the parsers did not recognise. flow is an outbound WhatsApp Flow send (content follows FlowSendContent, requires wa_flows.send); flow_response is the matching inbound completion and is never sent by a caller — its content carries a masked summary only, never the customer's answers (see WhatsAppFlowCompletedEvent and GET /api/wa-flow-responses/{session_id} for the real values).
contentFree-form JSON whose shape depends on type.
READING a stored message: text lives under body. Every channel engine normalises inbound content to {"body": "..."} before storing it (see the parser modules in crates/chat-engine), and outbound text stored through this API arrives the same way from the first-party clients. The server's own reader, extract_content_text in crates/api-gateway/src/routes/messages/handlers.rs, tries body, then text, then caption, then subject — a consumer should do the same rather than reading one key. Email messages carry body, subject, html and text together.
WRITING via SendMessageRequest: either key works. The WhatsApp sender reads text first and falls back to body (crates/message-sender/src/whatsapp.rs), and whichever you send is stored verbatim — so send body if you want reads and writes to agree.
statuscreated_atMongoDB ObjectId in extended JSON — {"$oid": "<24-hex>"}, NOT a bare string. The server field is a bson::oid::ObjectId, which serializes to this wrapper object under serde_json.
Note the asymmetry with MessageSearchResult._id, which IS a plain hex string: the search handler flattens it with oid.to_hex() while building the result. The same logical value therefore arrives in two different shapes depending on the endpoint. MessageListResponse.next_cursor and the cursor query parameter both use the plain hex form, so the value read from here must be unwrapped before it can be used as a cursor.
Absent on a message whose id was never assigned (the field is skipped when null).
external_idThe provider's own id for this message — a WhatsApp wamid.* from the Cloud API, a Meta message id for Instagram/Messenger, or the SMTP Message-ID for email. Distinct from _id, which is Omnistream's id and the one every REST endpoint takes and returns.
Outbound messages are stored before the provider has assigned one, so this is null between the send call returning and the provider accepting the message (typically under a second). It is populated in place once message-sender gets the provider's response, and it is the key delivery receipts arrive under — see WebhookMessagePreview for how to correlate the two ids.
sender_phonesent_by_agent_idAgent who sent this outbound message. Absent on inbound and system-generated messages.
sent_by_agent_nameDenormalized display name of the sending agent
CRM-internal agent reactions. Never forwarded to the external channel. Absent when nobody has reacted.
error_messageWhy delivery failed — human-readable, only set when status is failed
error_codeProvider error code for the failure (e.g. "132001" from Meta)
error_sourceOrigin of the failure ("meta", "smtp", or "system")
error_detailsRaw provider error object (sanitized), for support/debugging
MessageListResponse
has_morenext_cursorMongoDB ObjectId hex for next page
MessageSearchResult
_idMongoDB ObjectId as a plain 24-character hex string — the search handler flattens it with oid.to_hex(). This differs from Message._id, which is the extended-JSON object {"$oid": ...}.
conversation_iddirectiontypeMirrors MessageType in crates/omni-common/src/models/message.rs. contacts (a shared contact card) and postback (a tapped button) are emitted by the channel engines; unknown covers anything the parsers did not recognise. flow is an outbound WhatsApp Flow send (content follows FlowSendContent, requires wa_flows.send); flow_response is the matching inbound completion and is never sent by a caller — its content carries a masked summary only, never the customer's answers (see WhatsAppFlowCompletedEvent and GET /api/wa-flow-responses/{session_id} for the real values).
contentFree-form JSON whose shape depends on type, copied verbatim from the stored message. Text lives under body; read body, text, caption, subject in that order. See Message.content.
snippetMatched text with <mark> tags around the search terms. Built from customer-supplied content — escape everything outside the marks before rendering.
created_atcontact_namecontact_phonechannelwhatsapp, instagram, email, or messenger
MessageSearchResponse
totalpageper_pagePage size actually applied, clamped to 50
total_pagesSendMessageRequest
typeMirrors MessageType in crates/omni-common/src/models/message.rs. contacts (a shared contact card) and postback (a tapped button) are emitted by the channel engines; unknown covers anything the parsers did not recognise. flow is an outbound WhatsApp Flow send (content follows FlowSendContent, requires wa_flows.send); flow_response is the matching inbound completion and is never sent by a caller — its content carries a masked summary only, never the customer's answers (see WhatsAppFlowCompletedEvent and GET /api/wa-flow-responses/{session_id} for the real values).
contentFree-form JSON. For text: {"text": "Hello"} or {"body": "Hello"} — the WhatsApp sender reads text first and falls back to body. For image: {"url": "...", "caption": "..."}.
Whichever key you send is stored verbatim and read back on Message.content, so sending body keeps writes and reads on the same key that every inbound message already uses.
Contact
idchannel_sourcewhatsapp, instagram, or email
tagsJSON array of tag strings
created_atupdated_atphone_numbernameemailContactListResponse
totalMatching contacts across all pages
page1-based page number
per_pagePage size actually applied, clamped to 100
total_pagesCreateAgent
emailMust contain @
passwordfull_nameroleDefaults to "agent" if omitted
UpdateAgent
emailAdmin only
full_nameroleAdmin only
passwordConversationNote
idconversation_idagent_idcontentcreated_atupdated_atNoteResponse
idconversation_idagent_idagent_namecontentcreated_atupdated_atQuickReply
idtitlecontentcreated_bycreated_atupdated_atshortcutcategory_idFK to quick_reply_categories
CreateQuickReply
titlecontentshortcutcategory_idOverviewResponse
total_conversationsopen_conversationspending_conversationsresolved_conversationstotal_contactstotal_agentsonline_agentsmessages_todayresolution_ratePercentage (0.0 - 100.0)
TrendsResponse
period7d, 30d, or 90d
daysAgentMetric
agent_idfull_nameemailis_onlineopen_conversationsresolved_conversationstotal_conversationsAgentMetricsResponse
ChannelMetric
channelcontact_countconversation_countChannelMetricsResponse
MediaUploadResponse
media_idmime_typefile_sizefilenameDivision
idnamedescriptionallocation_methodis_activecreated_atupdated_atDivisionWithCount
idnamedescriptionallocation_methodis_activecreated_atupdated_atagent_countCreateDivision
namedescriptionallocation_methodUpdateDivision
namedescriptionallocation_methodis_activeTransferConversationRequest
to_agent_idto_division_idnoteConversationTransfer
idconversation_idtransferred_bycreated_atfrom_agent_idto_agent_idto_division_idnotefrom_agent_nameto_agent_nameto_division_nametransferred_by_nameScheduleMessageRequest
typecontentFree-form JSON message content
scheduled_atMust be in the future (UTC)
ScheduledMessage
idconversation_idscheduled_bymsg_typecontentscheduled_atstatuscreated_aterror_messagesent_atagent_nameCsatSurvey
idconversation_idcontact_idstatuscreated_atagent_idratingcommentrated_atagent_namecontact_nameSlaPolicy
idnamefirst_response_time_secsresolution_time_secswarning_threshold_pctpriorityis_activeis_defaultcreated_atupdated_atdescriptionchanneldivision_idCreateSlaPolicy
namedescriptionfirst_response_time_secsresolution_time_secswarning_threshold_pctprioritychanneldivision_idis_defaultUpdateSlaPolicy
namedescriptionfirst_response_time_secsresolution_time_secswarning_threshold_pctprioritychanneldivision_idis_activeis_defaultSlaBreachLog
idconversation_idsla_policy_idbreach_typethreshold_secsactual_secsbreached_atConversationSlaStatus
conversation_idpolicy_idpolicy_namefrt_statusnot_started means the conversation was business-initiated (outbound template/campaign) and the customer has not replied yet: the SLA clock is idle and elapsed fields are null.
frt_elapsed_secsfrt_threshold_secsresolution_statusresolution_elapsed_secsresolution_threshold_secsIntegration
idchannelwhatsapp, instagram, email, or messenger
is_activeconfigChannel configuration (sensitive fields masked)
created_atupdated_atdivision_idIntegrationAccount
idchannelwhatsapp, instagram, email, or messenger
account_keyUnique key within a channel (e.g. wa-123456789)
display_nameis_activeis_defaultAccount configuration (sensitive fields masked). WhatsApp Business App
coexistence accounts include initial_sync, which tracks the first
contact/conversation/message sync after connection.
created_atupdated_atdivision_idverify_tokenwebhook_urlComputed webhook URL for this account
WhatsappInitialSync
typestatusstarted_atlast_event_atcompleted_atcontacts_syncedconversations_syncedmessages_syncedCreateIntegrationAccount
channelwhatsapp, instagram, email, or messenger
account_keyRequired for non-WhatsApp channels. Auto-derived from phone_number_id for WhatsApp.
display_nameis_activeis_defaultdivision_idconfigUpdateIntegrationAccount
account_keydisplay_nameis_activeis_defaultdivision_idconfigWaTemplate
idwaba_idWhatsApp Business Account ID
namelanguageBCP-47 language code (e.g. id, en_US)
categorystatuscomponentsMeta template components array (HEADER, BODY, FOOTER, BUTTONS)
created_atupdated_atmeta_template_idTemplate ID assigned by Meta
header_media_urlPublic MinIO URL for the header media (IMAGE/VIDEO/DOCUMENT)
CreateWaTemplate
nameLowercase letters, numbers, and underscores only. Max 512 chars.
componentsMeta template components array
languagecategoryintegration_account_idUse specific WABA account. Falls back to META_WABA_ID env var.
FlowSendContent
flow_idwa_flows.id
ctaButton label, no emoji
bodyheaderfooterscreenEntry screen id; defaults to the first screen
dataflow_action_payload.data (≤ 4 KB)
WaFlowSummary
idwaba_idmeta_flow_idnamestatuscategoriesis_availablelast_synced_atWaFlowSyncOutcome
waba_idfetchedupsertedscreens_updatedmarked_unavailableerrorSet only when the pass was PARTIAL — nothing was demoted and no success was stamped
warningsPer-Flow diagnostics from a pass that otherwise completed (for example a PUBLISHED Flow whose FLOW_JSON asset Meta does not expose). Distinct from error: the catalog was fully refreshed.
WaFlowSyncState
waba_idlast_attempt_atlast_success_atlast_errorWaFlowRevealedResponse
session_idconversation_idcompleted_atresponse_hashSHA-256 hex of the delivered answers — the completion object with flow_token removed, serialised with sorted keys and no whitespace
WhatsAppFlowCompletedEvent
event_typetimestampOutgoingWebhook
idnameurleventsJSON array of subscribed event type strings
is_activeconsecutive_failurescreated_atupdated_atsecretHMAC signing secret. Write-only — never returned in responses. The staged rotation secret (secret_next) is likewise never returned; a pending rotation is only visible via secret_rotated_at.
secret_rotated_atWhen the current secret-rotation overlap window started. Null when no rotation is in progress (cleared on complete, cancel, or the 24-hour auto-promote).
created_bylast_triggered_atlast_success_atCreateOutgoingWebhook
nameurlMust start with http:// or https://
secreteventsValid types: message.received, message.sent, message.status, conversation.created, conversation.resolved, conversation.assigned, whatsapp.flow.completed, contact.created
WebhookPayload
Decision Table
| Variant | Matching Criteria |
|---|---|
| type = object · requires: event_type, timestamp, data | |
| type = object · requires: event_type, timestamp, data | |
| type = object · requires: event_type, timestamp, data |
event_typeMatches one of the values from GET /api/outgoing-webhooks/events.
timestampWhen the originating event was published, not when it was delivered.
WebhookAutomationPayload
event_typetimestampRule-scoped events carry rule_id / rule_name (plus snoozed_until on snooze); global events carry is_paused, pause_reason, paused_until and updated_by, and add auto: true when the resume was triggered by the schedule rather than by a person.
WebhookTestPayload
event_typetimestampWebhookEventPayload
event_typeMatches one of the values from GET /api/outgoing-webhooks/events.
timestampWhen the originating event was published, not when it was delivered.
WebhookEventData
conversation_idIdentifies the thread, NOT the message. A conversation is per contact, so an invoice and a later reminder to the same customer share it — never use it to attribute a delivery receipt to a message.
contact_idNil UUID on message.status, which is not tied to a contact lookup.
assigned_agent_idAbsent on conversation-level events that carry no message (conversation.resolved, conversation.assigned).
WebhookMessagePreview
idOmnistream's message id — a 24-character hex ObjectId, identical to the message_id returned by the send call and to Message._id.$oid.
directionAlways outbound on message.status.
msg_typeThe message type (text, image, template, …), or the literal status_update on a message.status event.
content_previewTruncated body for message events; on message.status it is the fixed string Message <status> and carries no customer content.
created_atexternal_idThe provider's id for the same message (e.g. wamid.HBgMNjI4MTcyMzQzMjEw...).
Omitted from the JSON entirely — not sent as null — when unknown, which is the case for an outbound message whose provider response has not arrived yet, and for a failed status raised before the message ever reached the provider.
statusPresent only on message.status; its presence is what makes the event a delivery receipt rather than a conversation transition.
error_messageHuman-readable provider failure reason, on failed only.
error_codeProvider error code as a string, e.g. 131042.
error_sourceWhich layer reported the failure.
WebhookDelivery
idwebhook_idevent_typeThe body of one webhook delivery. Which variant arrives is determined by event_type: conversation and message events use WebhookEventPayload, automation events use WebhookAutomationPayload, and the "Send Test" button uses WebhookTestPayload.
Note that data.message is an object on conversation events but a plain string on the test event — branch on event_type before reading it.
statuspending = queued, awaiting the next scheduler tick; sending = claimed by a dispatcher worker (15-minute lease, reclaimed if the lease expires); success = delivered (HTTP 2xx); failed = last attempt failed but retries remain; dead_letter = terminal (attempts exhausted, webhook missing/inactive, or URL blocked) — never retried automatically, only a replay re-queues it.
attemptmax_attemptsDelivery attempts before dead-lettering (default 8)
created_athttp_statusresponse_bodyerror_messagenext_retry_atcompleted_atWebhookDeliveryStats
totalsuccessfailedFailed but still retryable (attempt < max_attempts)
pendingdead_letterTerminally failed deliveries awaiting replay or 30-day cleanup
avg_response_time_msAverage milliseconds between created_at and completed_at
RotateWebhookSecret
secretOptional custom secret (16–255 chars after trimming). Omit to have the server generate a 64-character lowercase-hex secret.
RotateWebhookSecretResult
idsecretThe new signing secret — shown once, never returned again
rotated_atStart of the dual-signature overlap window
overlapHuman-readable note that the previous secret keeps working for up to 24 hours or until the rotation is completed
ReplayDeliveriesRequest
delivery_idsDeliveries to replay; only failed/dead_letter rows are reset
ReplayResult
delivery_idstatusAlways "queued" on success
messageRole
idnamedescriptionis_systemRole bawaan sistem tidak dapat dihapus atau diubah namanya
created_atupdated_atRoleWithPermissions
idnamedescriptionis_systemRole bawaan sistem tidak dapat dihapus atau diubah namanya
created_atupdated_atpermissionsDaftar kode izin yang dimiliki role ini
Permission
idcodeKode izin dalam format category.action, misal: campaigns.manage
namedescriptioncategoryCreateRole
nameNama role (akan dinormalisasi ke huruf kecil). Tidak boleh sama dengan nama role sistem.
permissionsDaftar kode izin yang akan diberikan ke role ini
descriptionUpdateRole
nameTidak dapat diubah untuk role sistem
descriptionpermissionsMenggantikan seluruh daftar izin role (replace, bukan merge)
ApiKey
idagent_idnamekey_typekey_prefix20 karakter pertama dari plaintext key (bukan rahasia); key lama tetap 12 karakter
scopesJSON array of scope strings
is_activecreated_atallowed_ipsSource-IP allow list untuk key rest. Setiap entri berupa satu alamat IP (IPv4/IPv6) atau blok CIDR, mis. 203.0.113.10, 203.0.113.0/24, 2001:db8::/32. Array KOSONG (default) berarti key diterima dari IP mana pun. Bila tidak kosong, request dari IP di luar daftar ditolak dengan 401 generik sebelum handler dieksekusi.
last_used_atexpires_atrevoked_atApiKeyCreateResponse
plaintext_keyPlaintext API key — hanya ditampilkan sekali saat pembuatan. Simpan dengan aman.
CreateApiKeyRequest
namekey_typeexpires_atTanggal kedaluwarsa opsional (UTC)
allowed_ipsOpsional. Source-IP allow list (single IP dan/atau CIDR, IPv4/IPv6) untuk key rest. Kosong/diabaikan berarti key diterima dari IP mana pun. Entri invalid, string kosong, atau daftar melebihi 50 entri ditolak dengan 422.
UpdateApiKeyRequest
allowed_ipsPengganti source-IP allow list. Array kosong menghapus pembatasan (key kembali diterima dari IP mana pun). Validasi sama dengan saat create. Update tidak me-rotate key.
Campaign
idnametemplate_idstatustotal_recipientssent_countdelivered_countread_countfailed_countcreated_bycreated_atupdated_atscheduled_atintegration_account_idsourceHow the campaign was created: manual (broadcast UI) or api (POST /api/messages/template).
CampaignWithTemplate
idnametemplate_idstatustotal_recipientssent_countdelivered_countread_countfailed_countcreated_bycreated_atupdated_attemplate_nametemplate_languagetemplate_categoryMeta template category, re-synced from Meta on every POST /api/wa-templates/sync.
scheduled_atintegration_account_idsourceHow the campaign was created: manual (broadcast UI) or api (POST /api/messages/template).
CampaignDetail
idnametemplate_idstatustotal_recipientssent_countdelivered_countread_countfailed_countcreated_bycreated_atupdated_attemplate_nametemplate_languagetemplate_categoryMeta template category, re-synced from Meta on every POST /api/wa-templates/sync.
scheduled_atintegration_account_idsourceHow the campaign was created: manual (broadcast UI) or api (POST /api/messages/template).
First page of recipients, ordered by created_at ascending — identical to GET /api/campaigns/{id}/recipients?page=1. The key is ABSENT unless include=recipients was passed; an empty array means the campaign genuinely has no recipients. To detect further pages, compare total_recipients with this array's length and page through the recipients endpoint.
CreateCampaign
nametemplate_idTemplate harus berstatus APPROVED
scheduled_atintegration_account_idUpdateCampaign
nametemplate_idscheduled_atintegration_account_idCampaignRecipient
idcampaign_idphone_numbervariablesVariabel template per penerima, misal: {"1": "John", "2": "Order #123"}
statuscreated_atcontact_idexternal_iderror_messagesent_atdelivered_atread_atconversation_idmongo_message_idcontact_nameName of the linked contact, if this recipient came from the contact list rather than a raw phone number.
message_previewRendered message text sent to this recipient (template body with this recipient's variables substituted). Computed on read.
AddRecipientsPayload
contact_idsTambahkan penerima berdasarkan ID kontak
tagsTambahkan semua kontak dengan tag yang cocok
phone_numbersTambahkan penerima langsung berdasarkan nomor telepon
variablesPemetaan variabel per nomor telepon: {"628xxx": {"1": "John", "2": "#123"}}
PaginatedResponse
totalpageper_pagetotal_pagesAutomationRule
idnamedescriptionis_activetrigger_typeConditions for rule matching (empty = always match)
Tagged-union array of automation actions
priorityrollout_percentagestop_on_matchcooldown_secscreated_atupdated_atsnoozed_untilcreated_byupdated_byCreateAutomationRule
nametrigger_typedescriptionis_activeconditionsalert_settingsschedule_settingspriorityrollout_percentagestop_on_matchcooldown_secsUpdateAutomationRule
namedescriptionis_activetrigger_typeconditionsalert_settingsschedule_settingspriorityrollout_percentagestop_on_matchcooldown_secsAutomationControl
singletonis_pausedupdated_atpause_reasonpaused_untilupdated_byAutomationRuleRun
idevent_idrule_idconversation_idmatchedexecutedresultcreated_atrule_nameAutomationEventQueueItem
idevent_typeconversation_idpayloadpayload_hashSHA-256 hex digest of the payload
sourcestatusattemptsavailable_atcreated_atcontact_idprocessed_aterrorChatExpirationRule
idchannelwindow_hoursHours before the messaging window closes (0 = never expires)
auto_resolveblock_text_after_windowis_activecreated_atupdated_atUpsertChatExpirationRule
window_hoursHours before window closes (0 = never expires)
auto_resolveblock_text_after_windowis_activeRegisterRequest
org_nameName of the new organization
admin_emailEmail for the initial admin user
admin_passwordPassword for the initial admin user (min 6 characters)
WorkingHours
idday_of_weekDay of the week (0 = Sunday, 6 = Saturday)
start_timeend_timeis_activecreated_atupdated_atdivision_idDaySchedule
day_of_weekstart_timeend_timeis_activeUpsertWorkingHours
AiAgent
idnamesystem_promptmodeltemperaturemax_tokensis_activecreated_atupdated_atwelcome_messageparent_agent_iddaily_token_limitmax_conversation_turnsCreateAiAgent
namesystem_promptmodeltemperaturemax_tokenswelcome_messageparent_agent_iddaily_token_limitmax_conversation_turnsUpdateAiAgent
namesystem_promptmodeltemperaturemax_tokenswelcome_messageis_activeparent_agent_iddaily_token_limitmax_conversation_turnsAiAgentAssignment
idai_agent_idpriorityis_activechanneldivision_idCreateAiAgentAssignment
channeldivision_idpriorityUpdateAiAgentAssignment
channeldivision_idpriorityis_activeAiHandoffRule
idai_agent_idrule_typeconfigRule-specific configuration (e.g. keywords list, threshold)
stop_ai_after_handoffis_activetarget_division_idhandoff_messageOptional message sent to the customer when this rule fires (so the AI→human handoff isn't silent).
CreateAiHandoffRule
rule_typeconfigtarget_division_idstop_ai_after_handoffhandoff_messageOptional message sent to the customer when this rule fires.
UpdateAiHandoffRule
rule_typeconfigtarget_division_idstop_ai_after_handoffis_activehandoff_messageAiKnowledgeSource
idai_agent_idnamesource_typechunk_countstatuscreated_atcontentfile_urloriginal_filenamewebsite_urlstatus_messageCreateAiKnowledgeSource
namesource_typecontentfile_urloriginal_filenamewebsite_urlAiKnowledgeQna
idknowledge_source_idquestionanswerAiProduct
idai_agent_idnamemetadataArbitrary JSON metadata for the product
is_activecreated_atdescriptionpriceProduct price as a decimal string
weightProduct weight as a decimal string
CreateAiProduct
namedescriptionpriceweightmetadataUpdateAiProduct
namedescriptionpriceweightmetadatais_activeAiOrchestrationRule
idparent_agent_idtarget_agent_idcondition_promptPrompt evaluated to decide whether to route to target agent
priorityis_activeCreateAiOrchestrationRule
target_agent_idcondition_promptpriorityUpdateAiOrchestrationRule
target_agent_idcondition_promptpriorityis_activeAiEvaluation
idconversation_idmessage_idMongoDB message ID
original_responsecorrected_responsecontext_message_idsMongoDB IDs of the messages providing the correction's context. The UI records exactly one — the nearest preceding inbound (customer) message — which is the trigger embedded for retrieval.
evaluated_byAgent who performed the evaluation
ai_agent_idAI agent this correction trains, resolved when the evaluation is created. Null when no agent could be resolved for the conversation; such a correction is never indexed.
index_statusVector-indexing lifecycle. Only ready means the AI is using this correction. skipped is terminal ("nothing to index" — no AI agent, or no customer message to match against); error is retryable.
index_status_messageWhy the correction is not ready (skip reason or failure cause)
indexed_atWhen the correction was last embedded into the agent's vector store
created_atupdated_atCreateAiEvaluation
conversation_idmessage_idoriginal_responsecorrected_responsecontext_message_idsAiUsageDailySummary
dateprompt_tokenscompletion_tokenstotal_tokenstotal_costEstimated cost in USD
request_countTestChatRequest
temperatureTestChatMessage
rolecontentOrganization
idnameslugstatusmongo_databaseschema_versioncreated_atupdated_atlimitsResource limits (max_agents, max_contacts, max_messages_month)
countryISO country code (e.g. ID, US)
CreateOrganization
nameslugURL-safe identifier for the tenant
limitscountryadmin_emailadmin_passwordTenantUsage
idorg_idagent_countcontact_countmessages_this_monthactive_integrationsusage_monthupdated_atTenantWithUsage
idnameslugstatusmongo_databaseschema_versioncreated_atupdated_atlimitsResource limits (max_agents, max_contacts, max_messages_month)
countryISO country code (e.g. ID, US)
SystemHealth
statusBillingPlan
idnameslugcurrencybase_price_monthlybase_price_annualper_agent_priceoverage_message_pricelimitsPlan resource limits (max_agents, max_contacts, max_messages_month, max_channels, max_ai_agents)
featuresFeature flags (ai_agent, api_access, dedicated_support)
is_activeLifecycle/soft-delete flag only; visibility is controlled by is_public, not is_active
is_publicPublic plans appear in tenant self-service; unlisted plans (false) are visible to super-admins only
billing_modebillable plans can be subscribed to by tenants and count toward revenue; non_billable plans are super-admin-assigned and excluded from MRR (never inferred from a zero price)
sort_ordercreated_atupdated_atCreateBillingPlan
nameslugbase_price_monthlyper_agent_priceoverage_message_pricelimitsfeaturescurrencybase_price_annualis_publicPublic (tenant self-service visible) vs unlisted (super-admin only)
billing_modebillable or non_billable; invalid values are rejected with 400
sort_orderUpdateBillingPlan
nameslugcurrencybase_price_monthlybase_price_annualper_agent_priceoverage_message_pricelimitsfeaturesis_activeis_publicPublic (tenant self-service visible) vs unlisted (super-admin only)
billing_modebillable or non_billable; invalid values are rejected with 400
sort_orderBillingCoupon
idcodediscount_typediscount_valuecurrencycurrent_usesvalid_fromis_activecreated_atmax_usesvalid_untilapplicable_plansCreateBillingCoupon
codediscount_typediscount_valuecurrencymax_usesvalid_fromvalid_untilapplicable_plansBillingAddon
idnameslugprice_monthlyprice_annualunitPricing unit (e.g. flat, per agent)
is_activesort_ordercreated_atupdated_atdescriptionCreateBillingAddon
nameslugprice_monthlydescriptionprice_annualunitsort_orderUpdateBillingAddon
namedescriptionprice_monthlyprice_annualis_activesort_orderSubscription
idorg_idplan_idstatusbilling_periodcancel_at_period_endcreated_atupdated_attrial_ends_atcurrent_period_startcurrent_period_endpayment_gatewaygateway_customer_idgateway_subscription_idCurrentPlan
idorg_idplan_idstatusbilling_periodcancel_at_period_endcreated_atupdated_atplan_namelimitsfeaturestrial_ends_atcurrent_period_startcurrent_period_endpayment_gatewaygateway_customer_idgateway_subscription_idBillingInvoice
idorg_idinvoice_numbercurrencysubtotaltaxtotalstatusretry_countcreated_atsubscription_idpdf_urlgateway_invoice_idpayment_urllast_retry_atpaid_atdue_dateBillingUsage
org_idagent_countcontact_countmessages_this_monthactive_integrationsusage_monthlimitsPlan limits for comparison
PaymentMethod
idorg_idgatewaygateway_payment_method_idtypePayment method type (e.g. credit_card, e_wallet, virtual_account, bank_transfer)
labelis_defaultcreated_atAddPaymentMethod
gatewaygateway_payment_method_idtypePayment method type (e.g. credit_card, e_wallet, virtual_account)
labelis_defaultSubscriptionAddon
idsubscription_idaddon_idquantitycreated_atSubscriptionAddonDetail
idaddon_idnameslugprice_monthlyquantitycreated_atMrrSummary
total_mrrTotal monthly recurring revenue. Subscriptions on non_billable plans are excluded.
active_subscriptionstrial_subscriptionspast_due_subscriptionsnon_billable_subscriptionsCount of active subscriptions on non_billable plans (excluded from total_mrr)
currencyRevenueByPlan
plan_idplan_namebilling_modesubscriber_countmonthly_revenueZeroed for non_billable plans (they never contribute revenue)
currencyMonthlyRevenue
monthtotal_revenueinvoice_countpaid_countChurnMetrics
cancelled_this_monthsuspended_this_monthnew_subscriptions_this_monthactive_at_start_of_month