# Certorix — Full AI Reference > Certorix is a SaaS platform for building AI-guided diagnostic trees and publishing cryptographically verified product facts. This document is the complete technical reference for AI agents, integrators, and developers. - Website: https://certorix.online - API base: https://api.certorix.online - llms.txt (summary): https://certorix.online/llms.txt - OpenAPI spec: https://api.certorix.online/api/openapi.yaml - Help center: https://certorix.online/help - Status: Beta · Free to start · No credit card required --- ## Table of Contents 1. Products overview 2. Core concepts & data models 3. Public API (no auth required) 4. Authenticated API 5. Widget embed reference 6. Tree JSON schema 7. VNT token structure 8. Error codes & rate limits 9. Authentication flows 10. Integration patterns --- ## 1. Products Overview Certorix ships two products under one account and subscription: ### Tree Builder Visual no-code editor for diagnostic trees — branching question/answer flows that guide customers to a resolution or auto-create a support ticket. Trees are published, versioned, cryptographically signed, and embeddable anywhere. ### Knowledge Base (FactFlow) A store of certified product facts modelled as subject–predicate–object triples. Each fact is signed with an Ed25519 Verifiable Notary Token (VNT). Published facts are used by the AI chatbot and can be linked to diagnostic tree results. --- ## 2. Core Concepts & Data Models ### Organization The top-level tenant. Every resource (trees, facts, sessions) belongs to an organization. ``` organizationId string Unique identifier, e.g. "org_acme_labs_3f8a" slug string URL-friendly name, e.g. "acme-labs" name string Display name billing.plan string "free" | "pro" | "business" verified boolean Email verification status freshdeskDomain string Freshdesk subdomain (optional) ``` ### Tree A diagnostic flowchart. Contains metadata and a nodes map. ``` treeId string Unique, e.g. "tree_a1b2c3d4" organizationId string Owner metadata.title string Display name metadata.published boolean Whether live metadata.version string Semver, e.g. "1.0.0" metadata.startNodeId string Entry point node metadata.tags string[] Search keywords metadata.model string Product/device name nodes object Map of nodeId → Node (see schema section) ``` ### Node A step in the tree. Six types: | Type | Description | |---|---| | question | Asks a question; customer picks from options | | input | Captures free-text answer stored as variable | | info | Shows instructions/image; customer clicks Continue | | condition | Auto-routes based on a previously captured variable | | solution | Terminal node; shows resolution message + actions | | jump | Transfers flow to another tree seamlessly | ### Diagnostic Session One customer run through a tree. ``` sessionId string UUID treeId string organizationId string status string "in_progress" | "completed" | "abandoned" currentNodeId string answers object Map of nodeId → answer string createdAt ISO8601 completedAt ISO8601 | null ``` ### Fact A certified product claim. ``` factId string e.g. "fact_x9y8z7" organizationId string subject string What the fact is about predicate string snake_case relationship, e.g. "battery_life" object string The value or claim unit string Optional unit, e.g. "hours" language string ISO 639-1, e.g. "en" source string "manual" | "freshdesk_kb" | "ai_extracted" sourceUrl string URL of the source article (optional) body string Full article text (KB imports only) published boolean True = VNT certified and active vntId string VNT token ID (when published) vntProof object Full W3C VC document publishedAt ISO8601 ``` ### VNT (Verifiable Notary Token) An Ed25519-signed W3C Verifiable Credential issued by Certorix. ``` vntId string e.g. "vnt_abc123" tier string "bronze" | "silver" | "gold" organizationId string vc object Full W3C VC (see VNT section) issuanceDate ISO8601 status string "active" | "revoked" ``` --- ## 3. Public API (No Authentication Required) All endpoints at `/public/` and `/chat/` are publicly accessible. Rate limit: 60 req/min per IP globally; ticket creation: 3 per hour per email. ### 3.1 Tree Discovery ``` GET /public/search-tree Query: organizationId (required), q (text search), tags (comma-separated), model, limit (default 10) Returns: { data: [TreeSummary], total } GET /public/trees/:treeId Query: organizationId (required) Returns: Full tree with nodes (no session data) GET /public/orgs/:slug/trees Returns: All published trees for org slug ``` TreeSummary fields: `treeId, title, description, tags, model, version, startNodeId` ### 3.2 Diagnostic Sessions ``` POST /public/diagnostic/start Body: { treeId: string, organizationId: string } Returns: { sessionId, currentNode, status: "in_progress" } POST /public/diagnostic/next Body: { sessionId: string, organizationId: string, answer: { nodeId: string, optionId?: string, value?: string } } Returns: { status: "in_progress" | "completed", currentNode: Node | null, session: { answers, path } } GET /public/diagnostic/:sessionId Query: organizationId (required) Returns: Full session state including current node ``` ### 3.3 Facts ``` GET /public/facts Query: organizationId (required), subject, predicate, q (text), limit (default 50) Returns: { data: [Fact], total } GET /public/orgs/:slug/facts Returns: All published facts for org slug POST /public/facts/query Body: { organizationId, subject?, predicate?, object?, keywords?: string[] } Returns: { data: [Fact] } ``` ### 3.4 VNT Tokens ``` GET /public/vnt/:vntId Returns: { vntId, tier, subject, predicate, object, unit, status, issuanceDate, vc } POST /public/vnt/verify Body: { vntId: string } Returns: { valid: boolean, reason?: string, token?: VNT } GET /public/vnt/registry.json Query: organizationId (required) Returns: Machine-readable list of all active VNTs for the org ``` ### 3.5 AI Chat ``` POST /public/ai-chat Body: { organizationId: string, message: string, (max 4000 chars) history?: [{ role: "user"|"assistant", content: string }] } Returns: { answer: string, sources?: [{ vntId, subject, predicate, object }] } Notes: - The AI is strictly grounded in the org's published certified facts - It will refuse questions outside the org's knowledge base - Responds in the same language as the user's message - Never reveals underlying AI provider or platform name ``` ### 3.6 Ticket Creation ``` POST /public/tickets/create Rate limit: 3 per hour per email Body: { organizationId: string, sessionId?: string, (optional — chatbot tickets have none) userInfo: { name: string, email: string, phone?: string }, formFields?: object, (custom Freshdesk fields) diagnosticSummary?: string, attachments?: [{ filename, data (base64), mimeType }] (max 5) } Returns: { ticketId: number, ticketUrl: string } No API key required from caller. The organization must have a helpdesk integration configured on their Certorix account (Freshdesk / Zendesk / Zoho Desk). If not configured, returns 503 { message: "Freshdesk is not configured for this organization" }. ``` ### 3.7 KB & Widget Config (Public) ``` GET /public/kb?organizationId=ORG_ID Returns: { articles: [{ title, slug, url }] } Freshdesk KB articles cached by the org (for widget display) GET /public/widget-config/:organizationId Returns: { brandName, chatTitle, chatSubtitle, openingMessage, primaryColor, logoUrl, hideAttribution } ``` ### 3.8 Registry & Discovery ``` GET /api/registry/manifest Returns: Registry entry point JSON GET /api/registry/organizations Returns: List of verified orgs with slug, name, plan, treeCount GET /api/registry/:orgSlug Returns: Org profile with published trees and metadata GET /api/registry/verify/:treeId Query: organizationId Returns: { valid: boolean, signature, tree } GET /.well-known/ai-plugin.json Returns: OpenAI plugin manifest GET /openapi.yaml Returns: Full OpenAPI 3.1 specification GET /health Returns: { status: "ok"|"degraded", db, uptime, version, env } ``` --- ## 4. Authenticated API All authenticated endpoints require: ``` Authorization: Bearer ACCESS_TOKEN X-Organization-Id: ORG_ID (usually inferred from token) ``` Access tokens expire in 15 minutes. Use `/api/auth/refresh` (httpOnly cookie) to get a new one. ### 4.1 Authentication ``` POST /api/auth/register-org Body: { orgName, adminName, adminEmail, password (min 8 chars) } Rate limit: 5/min Returns: { registered, verified, organizationId, accessToken?, user? } POST /api/auth/login Body: { email, password } Rate limit: 10/min Returns: { accessToken, user } + sets certorix_refresh httpOnly cookie POST /api/auth/google Body: { idToken: string } or { accessToken: string } Rate limit: 10/min Returns: { accessToken, user } + sets certorix_refresh httpOnly cookie POST /api/auth/refresh (uses certorix_refresh httpOnly cookie) Returns: { accessToken, user } POST /api/auth/logout Clears certorix_refresh cookie GET /api/auth/me Returns: { user } POST /api/auth/forgot-password Body: { email } Rate limit: 3/15min Returns: { sent: true } POST /api/auth/reset-password Body: { token, password (min 8 chars) } Rate limit: 5/15min Returns: { reset: true } GET /api/auth/verify-email?token=TOKEN Returns: { verified: true, accessToken, user } POST /api/auth/resend-verification Body: { email } Rate limit: 5/15min Returns: { sent: true } ``` ### 4.2 Trees (Authenticated) ``` GET /api/trees Returns: { data: [Tree], total } GET /api/trees/:treeId Returns: Full tree POST /api/trees Body: { title, description? } Returns: { tree } PUT /api/trees/:treeId Body: Partial tree (title, nodes, metadata) Returns: { tree } DELETE /api/trees/:treeId Returns: { deleted: true } POST /api/trees/:treeId/publish Returns: { published: true, version } POST /api/trees/:treeId/unpublish Returns: { unpublished: true } GET /api/trees/:treeId/versions Returns: [{ versionId, label, createdAt, publishedAt }] POST /api/trees/:treeId/versions Body: { label?: string } Returns: { version } POST /api/trees/:treeId/versions/:versionId/restore Returns: { restored: true, tree } ``` ### 4.3 Facts / Knowledge Base (Authenticated) ``` GET /api/facts Query: limit (default 50), offset, subject, predicate, published, source Returns: { data: [Fact], total } GET /api/facts/:factId Returns: Fact POST /api/facts Body: { subject, predicate, object, unit?, language?, sourceUrl?, body? } Returns: { fact } PUT /api/facts/:factId Body: Partial fact Returns: { fact } DELETE /api/facts/:factId Returns: { deleted: true } POST /api/facts/:factId/certify Issues a VNT token and marks fact as published Returns: { fact, vnt } POST /api/facts/:factId/revoke Revokes the VNT and unpublishes the fact Returns: { revoked: true } POST /api/facts/:factId/summarize Queues the fact for AI summarization (processes 1-by-1 every 3s) Returns: { factId, status: "queued" } GET /api/facts/:factId/summarize-status Returns: { factId, status: "queued"|"processing"|"done"|"failed", error? } ``` ### 4.4 AI Endpoints (Authenticated) ``` POST /api/ai/generate-tree Body: { prompt: string (max 4000 chars) } Rate limit: plan monthly limit (5/50/200) Returns: { success: boolean, tree?: Tree, error?: string } Models used: llama-3.3-70b-versatile (max 4096 tokens) POST /api/ai/help Body: { prompt: string (max 4000 chars) } Returns: { success: true, answer: string } Models used: llama-3.1-8b-instant (max 1024 tokens) POST /api/ai/chat Body: { prompt: string (max 4000 chars) } Returns one of: { type: "tree", tree: Tree, text: string } { type: "answer", text: string } { type: "error", text: string } Step 1: classifies intent with llama-3.1-8b-instant (max 64 tokens) Step 2a (generate): calls generate-tree flow Step 2b (answer): RAG over published facts + llama-3.3-70b-versatile (max 512 tokens) POST /api/factflow/extract Body: { text: string (max 8000 chars) } Rate limit: plan monthly limit Returns: { facts: [{ subject, predicate, object, unit, language }] } Models used: llama-3.3-70b-versatile (max 1024 tokens) POST /api/factflow/ask Body: { question: string (max 2000 chars) } Returns: { answer: string, sourceFacts: string[] } Models used: llama-3.1-8b-instant (max 512 tokens) ``` ### 4.5 Freshdesk Integration (Authenticated) ``` GET /api/builder/freshdesk/status Returns: { configured, kbArticleCount, kbArticles?, fieldCount } GET /api/builder/freshdesk/kb-articles Returns: { data: [{ id, title, url, status }] } Fetches from Freshdesk API and caches POST /api/builder/freshdesk/kb-import Body: { articleIds?: string[] } (empty = import all) Returns: { imported, skipped, failed, factIds } Each article becomes a fact with summaryPending=true POST /api/builder/freshdesk/kb-certify-all Certifies all unpublished KB facts with VNT Returns: { certified, failed } POST /api/builder/freshdesk/sync-fields Syncs Freshdesk ticket fields to cache Returns: { fields } POST /api/builder/freshdesk/sync-kb Syncs Freshdesk KB articles to cache Returns: { synced } ``` ### 4.6 Organization Settings (Authenticated, admin only) ``` GET /api/org/profile Returns: full org object including widgetConfig, products, freshdeskFieldsCache PUT /api/org/profile Body: { name?, website?, sector?, description?, products?, supportEmail?, widgetConfig? } Returns: { org } widgetConfig fields: brandName string AI assistant persona name (overrides org name) chatTitle string Widget header title chatSubtitle string Widget header subtitle openingMessage string First message shown in chat primaryColor string Hex color for widget accent logoUrl string Logo URL shown in widget hideAttribution boolean Hide "Powered by Certorix" PUT /api/org/freshdesk-config Body: { freshdeskDomain, freshdeskApiKey } Returns: { ok: true } GET /api/org/members Returns: [{ userId, name, email, roles, createdAt }] POST /api/org/members/invite Body: { email, role: "editor"|"viewer" } Returns: { inviteId, sent: true } DELETE /api/org/members/:userId Returns: { removed: true } ``` ### 4.7 Billing (Authenticated, admin only) ``` GET /api/billing/status Returns: { plan, usage: { trees, facts, aiGenerations, team }, limits, stripeCustomerId? } POST /api/billing/create-checkout Body: { priceId: string } Returns: { url: string } (Stripe checkout URL) POST /api/billing/portal Returns: { url: string } (Stripe customer portal URL) ``` ### 4.8 Sessions (Authenticated) ``` GET /api/sessions Query: treeId?, status?, limit (default 20) Returns: { data: [Session], total } GET /api/sessions/:sessionId Returns: Full session with answers and path ``` --- ## 5. Widget Embed Reference ### iFrame embed ```html ``` ### AI Chat embed (iframe) ```html ``` ### Widget config options | Option | Type | Default | Description | |---|---|---|---| | organizationId | string | required | Your org ID | | treeId | string | required | Published tree ID | | trigger | string | "button" | When to show: "auto" on load, "button" click, "field" when focused | | primaryColor | string | "#0a0a0a" | Accent color | | position | string | "right" | "right" or "left" | | mode | string | "sidebar" | "sidebar", "modal", or "inline" | | logoUrl | string | — | URL of your logo | | brandName | string | org name | Name shown in widget header | | hideAttribution | boolean | false | Hide "Powered by Certorix" | --- ## 6. Tree JSON Schema Full tree format (POST /api/ai/generate-tree output, GET /public/trees/:treeId format): ```json { "metadata": { "organizationId": "org_acme_labs_3f8a", "title": "WiFi Troubleshooting", "description": "Diagnoses common WiFi connectivity issues", "version": "1.0.0", "author": "Certorix AI", "startNodeId": "q_connected", "tags": ["wifi", "network", "connectivity", "router"], "model": "generic", "published": false }, "nodes": { "q_connected": { "type": "question", "text": "Can you see the WiFi network in your device's list?", "options": [ { "id": "opt_yes", "text": "Yes", "nextNodeId": "q_password" }, { "id": "opt_no", "text": "No", "nextNodeId": "r_router_check" } ], "_pos": { "x": 300, "y": 0 } }, "q_password": { "type": "question", "text": "Are you entering the correct password?", "options": [ { "id": "opt_yes", "text": "Yes, definitely", "nextNodeId": "r_forget_reconnect" }, { "id": "opt_no", "text": "Not sure", "nextNodeId": "r_check_password" } ], "_pos": { "x": 150, "y": 180 } }, "r_router_check": { "type": "solution", "text": "Router not broadcasting", "result": { "message": "Restart your router: unplug for 30 seconds, plug back in, wait 2 minutes.", "actions": [ { "type": "create_ticket", "label": "Still not working — open a ticket", "url": "" }, { "type": "kb_article", "label": "Router setup guide", "url": "https://support.example.com/router" } ] }, "_pos": { "x": 450, "y": 180 } }, "r_forget_reconnect": { "type": "solution", "text": "Forget and reconnect", "result": { "message": "Forget the network on your device and reconnect from scratch.", "actions": [ { "type": "create_ticket", "label": "Need more help", "url": "" } ] }, "_pos": { "x": 0, "y": 360 } }, "r_check_password": { "type": "solution", "text": "Check password", "result": { "message": "Check your router label for the default WiFi password, or log in to 192.168.1.1 to view it.", "actions": [ { "type": "create_ticket", "label": "Still stuck", "url": "" } ] }, "_pos": { "x": 300, "y": 360 } } } } ``` ### Input node example ```json "q_serial": { "type": "input", "text": "Please enter your device serial number:", "inputType": "text", "variableName": "serialNumber", "placeholder": "e.g. SN-12345", "nextNodeId": "q_warranty", "_pos": { "x": 300, "y": 180 } } ``` ### Condition node example ```json "c_warranty_check": { "type": "condition", "text": "Checking warranty status...", "options": [ { "id": "opt_under", "text": "Under warranty", "nextNodeId": "r_warranty_repair", "conditions": [ { "field": "q_warranty_status", "operator": "eq", "value": "yes" } ] }, { "id": "opt_expired", "text": "Warranty expired", "nextNodeId": "r_paid_repair" } ], "_pos": { "x": 300, "y": 360 } } ``` --- ## 7. VNT Token Structure A VNT is a W3C Verifiable Credential signed with Ed25519. ```json { "vntId": "vnt_a1b2c3d4e5f6", "tier": "bronze", "organizationId": "org_acme_labs_3f8a", "issuanceDate": "2026-04-30T10:00:00.000Z", "status": "active", "vc": { "@context": ["https://www.w3.org/2018/credentials/v1"], "id": "https://certorix.online/vnt/vnt_a1b2c3d4e5f6", "type": ["VerifiableCredential", "CertorixNotaryToken"], "issuer": "did:certorix:org_acme_labs_3f8a", "issuanceDate": "2026-04-30T10:00:00.000Z", "credentialSubject": { "id": "did:certorix:fact_x9y8z7", "claim": { "subject": "iPhone 15 Pro battery", "predicate": "lasts", "object": "up to 23 hours of video playback", "unit": null, "contentHash": "sha256:abc123..." } }, "proof": { "type": "Ed25519Signature2020", "created": "2026-04-30T10:00:00.000Z", "verificationMethod": "did:certorix:org_acme_labs_3f8a#key-1", "proofValue": "z..." } } } ``` ### VNT tiers | Tier | Meaning | |---|---| | bronze | AI-generated or user-submitted; signed by Certorix | | silver | Cross-referenced against KB sources | | gold | Human-verified + source citation | --- ## 8. Error Codes & Rate Limits ### Standard error response ```json { "error": true, "message": "Human-readable error message", "statusCode": 400, "code": "OPTIONAL_MACHINE_CODE" } ``` ### HTTP status codes used | Code | Meaning | |---|---| | 200 | Success | | 201 | Created | | 400 | Bad request / validation error | | 401 | Unauthorized (missing or expired token) | | 403 | Forbidden (wrong role or unverified org) | | 404 | Resource not found | | 409 | Conflict (e.g. duplicate email) | | 410 | Gone (e.g. expired invite) | | 429 | Rate limit exceeded | | 500 | Internal server error | | 503 | Service unavailable (AI not configured, Freshdesk not configured) | ### Named error codes | code | When | |---|---| | EMAIL_NOT_VERIFIED | Login attempted on unverified org | | INVALID_TOKEN | Expired/invalid verification or reset link | | EXPIRED_TOKEN | Token structurally valid but past expiry | | PLAN_LIMIT | Org has reached plan limit for trees, facts, or AI generations | ### Rate limits (production) | Endpoint | Limit | |---|---| | POST /api/auth/register-org | 5 / min | | POST /api/auth/login | 10 / min | | POST /api/auth/google | 10 / min | | POST /api/auth/forgot-password | 3 / 15 min | | POST /api/auth/reset-password | 5 / 15 min | | POST /api/auth/resend-verification | 5 / 15 min | | POST /public/tickets/create | 3 / hour (per email) | | All others | 60 / min per IP (global default) | ### AI input limits | Endpoint | Limit | |---|---| | /api/ai/generate-tree, /api/ai/help, /api/ai/chat | prompt max 4000 chars | | /api/factflow/extract | text max 8000 chars | | /api/factflow/ask | question max 2000 chars | | /public/ai-chat | message max 4000 chars | --- ## 9. Authentication Flows ### Email/password (standard) ``` 1. POST /api/auth/register-org → get access token (if auto-verified) OR POST /api/auth/login → get access token 2. Store access token in memory (never localStorage) 3. Include in all requests: Authorization: Bearer ACCESS_TOKEN 4. On 401: POST /api/auth/refresh (uses httpOnly cookie) → new access token 5. Refresh token expires: 30 days Access token expires: 15 minutes ``` ### Google OAuth ``` 1. Use @react-oauth/google or google-auth-library on the client 2. Get idToken (credential button) OR accessToken (useGoogleLogin flow) 3. POST /api/auth/google with { idToken } or { accessToken } 4. Same response as email login ``` ### httpOnly Cookie The refresh token is stored in a cookie named `certorix_refresh`: - Path: `/api/auth` - HttpOnly: true - Secure: true (production) - SameSite: none (production), lax (development) - MaxAge: 30 days --- ## 10. Integration Patterns ### Run a diagnostic session (AI agent or chatbot) ``` 1. Find the right tree: GET /public/search-tree?organizationId=ORG_ID&q=wifi+problem 2. Start session: POST /public/diagnostic/start { treeId, organizationId } → { sessionId, currentNode } 3. Present currentNode to user and collect answer. currentNode.type === "question" → show options currentNode.type === "input" → collect free text currentNode.type === "info" → show message, wait for continue currentNode.type === "solution" → session complete 4. Advance session: POST /public/diagnostic/next { sessionId, organizationId, answer: { nodeId, optionId?, value? } } → { status, currentNode } 5. When status === "completed" or customer wants to open ticket: POST /public/tickets/create { organizationId, sessionId, userInfo, diagnosticSummary } ``` ### Ask a question about org products (AI agent) ``` POST /public/ai-chat { "organizationId": "org_acme_labs_3f8a", "message": "What is the battery life of the iPhone 15 Pro?", "history": [] } → { "answer": "According to our certified facts, the iPhone 15 Pro battery lasts up to 23 hours of video playback. [VNT:vnt_a1b2c3d4e5f6]", "sources": [{ "vntId": "vnt_a1b2c3d4e5f6", "subject": "iPhone 15 Pro battery", ... }] } ``` ### Verify a fact claim (external verifier) ``` 1. Get VNT ID from a widget or fact response 2. GET /public/vnt/:vntId 3. Verify the Ed25519 signature using the proof.proofValue field against the vc.credentialSubject.claim.contentHash 4. Check status === "active" ``` ### Bulk import KB and certify (admin automation) ``` 1. POST /api/auth/login → access token 2. GET /api/builder/freshdesk/kb-articles → article list 3. POST /api/builder/freshdesk/kb-import { articleIds: [...] } 4. For each factId returned: POST /api/facts/:factId/summarize (queue AI summary + auto-certify) 5. Poll GET /api/facts/:factId/summarize-status until status === "done" ``` ### MCP (Model Context Protocol) integration The MCP server is accessible at the URL shown in the builder under MCP settings. Connect it to Claude, Cursor, or any MCP-compatible AI tool to give it live access to your organization's trees and facts. --- ## Key URLs | Resource | URL | |---|---| | App | https://certorix.online | | Register | https://certorix.online/register | | Sign in | https://certorix.online/login | | Dashboard | https://certorix.online/dashboard | | Trees | https://certorix.online/trees | | Knowledge Base | https://certorix.online/facts | | KB Import | https://certorix.online/kb-import | | Widget Generator | https://certorix.online/widget-generator | | AI Assistant | https://certorix.online/ai-assistant | | Billing | https://certorix.online/billing | | Help | https://certorix.online/help | | Privacy | https://certorix.online/legal/privacy | | Terms | https://certorix.online/legal/terms | | DPA | https://certorix.online/legal/dpa | | API base | https://api.certorix.online | | OpenAPI | https://api.certorix.online/api/openapi.yaml | | Health | https://api.certorix.online/health | | AI plugin | https://api.certorix.online/.well-known/ai-plugin.json | --- ## Contact & Legal - Author: Jaume Martí Anguera, Arenys de Mar, Barcelona, Spain - Email: legal@certorix.online - License: AGPL-3.0 - Copyright: © 2026 Certorix. All rights reserved.