Data model
A map of the workspace, ticket, knowledge, and configuration records.
This reference describes the main records used by answerLoops and how they connect. Use it to find where a piece of workspace, ticket, or knowledge data lives. The schema definitions are in lib/db/schema.ts; migrations define changes applied to an existing database.
How the records relate
Almost everything belongs to a workspace (orgs). People reach a workspace through memberships, tickets collect replies and history, and knowledge articles can come from an imported source or be promoted from a ticket.
The diagram shows the key relationships only. The link from an article to its import source (source_id) is a plain reference rather than an enforced foreign key. The tables below list every record.
Workspace and access
| Records | Purpose |
|---|---|
orgs | Workspace identity and configuration. |
users, memberships | User accounts and their workspace roles. Each user row records the sign-in provider's account id, when the terms of service were accepted and which version, an explicit marketing opt-in, and the last sign-in. |
trial_claims | One row per person who has started the free trial, keyed by a hash of the normalized email and the provider account id (each unique). It is kept when a workspace or account is deleted, so a trial cannot be restarted. |
invitations | Pending team invitations. |
api_keys | API key metadata and permissions. |
Support conversations
| Records | Purpose |
|---|---|
tickets | Question, source, category, priority, status, AI draft state, and the reason a ticket went to a person without a draft. |
ticket_replies | Every message in a ticket's conversation after the first: staff replies, customer follow-ups, replies answerLoops posted, and maintainer messages kept for context. Each row records who wrote it (author_role) and the channel's own message id, which makes redeliveries harmless and lets a reply to a reply find its ticket. |
ticket_events | Recorded changes in the ticket's history. |
ticket_links | Relationships between tickets. |
ai_assessments | Results of AI assessment. |
ticket_feedback | Positive and negative answer feedback. |
csat_ratings | Customer satisfaction ratings. |
Knowledge
| Records | Purpose |
|---|---|
kb_sources | Imported source metadata. |
kb_articles | Searchable knowledge articles, their publication state, which team member added each one (created_by, null when unknown or imported), and whether a person has verified the answer (verified_at, verified_by, verified_content_hash, verification_note; all null until someone verifies it), and, for a saved resolution, when a repo sync last changed documentation related to it (docs_changed_at, docs_changed_source; the notice shows only while that is later than the last save and the last verification). |
kb_sync_jobs | Progress of background imports. |
kb_article_events | An append-only history of each article: created, promoted from a ticket, edited, verified by a person, and the review decisions behind it. Each row records the type, who did it (empty for imports and syncs), where it came from, and a hash of the question and answer at that moment. The article text itself is not copied. |
kb_answers | One row per generated answer: an id, the channel, whether it was generated or failed, when, and, for a support ticket's draft, the ticket's number. No question or answer text and no visitor or ticket details. Kept 90 days. |
kb_answer_references | The articles supplied to each answer: their order, retrieval score and a fingerprint of the article's question and answer at that moment. Deleting an article keeps the row with the article cleared. |
kb_support_settings | One row per workspace, created when a switch is first changed: which channels assess how well articles support an answer, the sampling rate, the daily limit, and whether short evidence text may be stored. Everything is off or unset until an owner or admin changes it. |
kb_support_assessments | One row per answer and assessment method version: whether the check completed, failed or was skipped (and why), the overall result for the answer, the model used, and the tokens spent. Removed with its answer, so it follows the same 90 days. |
kb_support_article_findings | For each assessed article on an answer: supporting evidence found, no supporting evidence found, or inconclusive, with the fixed reasons and any articles that carry the same support. A pair with no row was not assessed. Deleting an article keeps the row, with the fingerprint of the version that was supplied. |
kb_support_claim_findings | For each claim tied to a passage: the label (supports, partial, contradicts or inconclusive) and the passage number. When a workspace has turned on evidence storage it also holds one sentence of the answer and one of the article, each under 300 characters, which are deleted when they expire or when the setting is turned off. The finding stays. |
kb_support_flags | A team member's agreement or disagreement with a finding, with an optional note of up to 500 characters. Kept apart from the findings, which a flag never changes. |
kb_article_usage | A daily count, per article, of the answers it was supplied to as context. Counts only: no question text, visitor or ticket is stored. |
kb_review_items | Unseen questions waiting for a person to review, edit and approve them into articles. |
ticket_embeddings | Search representations associated with tickets. |
faq_snapshots | Generated FAQ content. |
Product feedback
| Table | Contents |
|---|---|
product_feedback | Feedback entries from the in-app widget: text, moderation status, and the team's reply. Shared across workspaces; each row records its author's workspace for display. |
product_feedback_votes | One row per person per approved entry they up-voted, with the voter's workspace so the votes go when the workspace does. Counts are computed from these rows at read time; nothing identifies a voter to other users. |
product_updates | The "what's new" feed shown on the widget's Updates tab. |
Changes to the feedback, votes and updates tables notify connected dashboards so the widget updates live.
Configuration and billing
Integration records store channel configuration, and credentials are stored protected rather than in plain text. Lookups are always made for one platform at a time. At most one enabled Slack, Discourse or Google Chat integration can hold a given workspace, site or space (team_id).
Message de-duplication is scoped to the workspace: tickets is unique on (org_id, source_message_id) and email_messages on (org_id, rfc_message_id). Telegram tickets use telegram:<chat id>:<message id> as their source message id, because Telegram message ids repeat across chats; tickets created before that format keep the bare message id and are still recognised as duplicates.
ai_configs stores workspace model settings. sla_configs stores response and resolution targets per priority. Rows with a null org_id are the global defaults; a workspace that saves its own targets gets its own row (unique per org_id and priority, removed with the workspace). Deadlines for a ticket use the workspace's row for the ticket's priority and fall back to the default row. subscriptions records hosted-plan state, and api_generations records standalone answer generation for usage accounting.
Use application actions and documented APIs for routine changes. For self-hosted upgrades, apply the documented migrations and retain a tested backup. See Upgrading.