AI Manager

The AI Manager is a general-purpose completion layer that wraps every LLM call on the platform. Beyond basic chat completions, it provides two optional capabilities that make it particularly useful for applications that need persistent context: supplementary data injection and automatic fact extraction.

Basic completion

At its simplest, POST /v1/ai/complete runs a chat completion using whichever AI provider is configured for your workspace (OpenAI or a local Ollama model).

Run a chat completion. Returns the assistant's response text, token usage, and any facts extracted from the conversation.

Request body
NameTypeRequiredDescription
systemPromptstringrequiredInstructions for the model — sets tone, role, and constraints
messagesMessage[]requiredConversation history. Each item: { role: "user" | "assistant" | "system", content: string }
jsonbooleanoptionalWhen true, instructs the model to respond with a valid JSON object
temperaturenumberoptionalSampling temperature 0–2. Lower = more deterministic (default: model default)
max_tokensnumberoptionalMaximum tokens in the completion
supplementarySupplementaryItem[]optionalStructured data blocks injected after the system prompt. See Supplementary Data.
extractionExtractionConfigoptionalFact extraction config. See Extraction.
Response
{
  "content": "Based on your runway of 14 months, hiring now makes sense if...",
  "usage": {
    "promptTokens": 312,
    "completionTokens": 189,
    "totalTokens": 501
  },
  "extracted": []   // populated when extraction is enabled
}
curl -X POST http://localhost:3001/v1/ai/complete \
  -H "x-api-key: sk_live_xxx" \
  -H "Content-Type: application/json" \
  -d '{
    "systemPrompt": "You are a strategic business advisor.",
    "messages": [
      { "role": "user", "content": "Should we expand into the EU market?" }
    ]
  }'

Supplementary data

Supplementary data lets you attach structured context to any completion call — company financials, user profiles, CRM data, live metrics — without manually formatting it into your system prompt. Each item is categorized and injected automatically between the system prompt and the conversation.

How it works

Items are sorted by priority (high first) and appended to your system prompt inside a clearly delimited block:

[System prompt you provided]

--- SUPPLEMENTARY CONTEXT ---
[CURRENT FINANCIALS] (high priority)
{
  "mrr": 85000,
  "runway_months": 14,
  "burn_rate": 42000
}

[EU MARKET OVERVIEW]
EU SaaS market grew 22% YoY. GDPR compliance required.
--- END SUPPLEMENTARY CONTEXT ---

The supplementary array

categorystringrequiredIdentifies the type of data (e.g. company-financials, user-profile, crm-data). Used as the section heading.
dataobject | stringrequiredThe context payload. Objects are JSON-formatted; strings are inserted verbatim.
labelstringoptionalCustom heading shown in the injected block. Defaults to category if omitted.
priority"high" | "medium" | "low"optionalSorting order within the block. High-priority items appear first. Defaults to medium.
Supplementary data is ideal for data your application already has — you retrieved it from your database, not from the user. For information you want to learn from the user's messages, use extraction below.
const res = await fetch('/v1/ai/complete', {
  method: 'POST',
  headers: {
    'x-api-key': 'sk_live_xxx',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    systemPrompt: 'You are a strategic business advisor.',
    messages: [
      { role: 'user', content: 'Should we expand into the EU market?' },
    ],
    supplementary: [
      {
        category: 'company-financials',
        label: 'Current Financials',
        priority: 'high',
        data: {
          mrr: 85000,
          runway_months: 14,
          burn_rate: 42000,
        },
      },
      {
        category: 'market-research',
        label: 'EU Market Overview',
        priority: 'medium',
        data: 'EU SaaS market grew 22% YoY. GDPR compliance required.',
      },
    ],
  }),
})

Fact extraction

When extraction is set, PulseLABS runs a secondary LLM call after the main completion to parse structured facts out of the user's message. Extracted facts are returned in the response and, when persist: true, saved to your account for retrieval via GET /v1/ai/extracted-data.

How it works

The extractor is a fast, low-temperature model call (pinned to gpt-4o-mini when using OpenAI). It reads the user message and returns a list of typed key-value pairs filtered to only the categories you allow. Facts are upserted by (source, category, key) — re-running on the same user always updates rather than duplicates.

The extraction config

sourcestringrequiredLogical namespace for these facts (e.g. "onboarding-chat", "support-ticket"). Enables filtering later.
categoriesstring[]requiredWhitelist of categories the extractor is allowed to emit. Only matching facts are returned and persisted.
persistbooleanoptionalWhen true, extracted facts are upserted to the database. When false (default), they are returned in the response but not stored.
Extraction never throws — if the secondary model call fails, the main completion is unaffected and extracted returns an empty array.
const res = await fetch('/v1/ai/complete', {
  method: 'POST',
  headers: {
    'x-api-key': 'sk_live_xxx',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    systemPrompt: 'You are a helpful assistant.',
    messages: [
      {
        role: 'user',
        content: 'My name is Sarah, I run a SaaS company in Berlin with 12 employees and €40k MRR.',
      },
    ],
    extraction: {
      source: 'onboarding-chat',
      categories: ['person', 'company', 'financials'],
      persist: true,
    },
  }),
})
const { content, extracted } = await res.json()
// extracted = [
//   { category: 'person',     key: 'name',     value: 'Sarah',   confidence: 0.98 },
//   { category: 'company',    key: 'location',  value: 'Berlin',  confidence: 0.95 },
//   { category: 'company',    key: 'employees', value: '12',      confidence: 0.93 },
//   { category: 'financials', key: 'mrr',       value: '40000',   confidence: 0.91 },
// ]

Using both together

Supplementary data and extraction are designed to work together in a feedback loop. You inject what you already know, extract what you learn, and on the next call inject the freshly-learned facts:

1
First message
User sends their first message. Extraction runs and persists facts about them.
2
Retrieve facts
Your app calls GET /v1/ai/extracted-data?source=onboarding-chat to load saved facts.
3
Next completion
Facts from step 2 are passed as supplementary data — the model now knows the user's context without it being in the conversation history.
4
Repeat
Each turn enriches the fact store. Context grows automatically without bloating the message history.
const res = await fetch('/v1/ai/complete', {
  method: 'POST',
  headers: {
    'x-api-key': 'sk_live_xxx',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    systemPrompt: 'You are a strategic business advisor.',
    messages: [
      { role: 'user', content: 'We have 14 months of runway. Should we hire now?' },
    ],
    // Inject structured context into the prompt
    supplementary: [
      {
        category: 'company-financials',
        priority: 'high',
        data: { mrr: 85000, runway_months: 14, burn_rate: 42000 },
      },
      {
        category: 'hiring-pipeline',
        data: { open_roles: ['Senior Engineer', 'Head of Sales'], avg_ramp_weeks: 8 },
      },
    ],
    // Extract facts from the user's message and persist them
    extraction: {
      source: 'strategy-chat',
      categories: ['financials', 'intent'],
      persist: true,
    },
    temperature: 0.4,
    max_tokens: 800,
  }),
})

const { content, usage, extracted } = await res.json()
console.log('Answer:', content)
console.log('Tokens used:', usage.totalTokens)
console.log('Facts learned:', extracted)

Managing extracted facts

List facts extracted and persisted across all AI completions. Filterable by source and category.

Path / Query parameters
NameTypeRequiredDescription
sourcestringoptionalFilter by the extraction source label
categorystringoptionalFilter by category (e.g. person, financials)
limitnumberoptionalMax records returned. Default 100, max 500.
Response
{
  "data": [
    {
      "id": "ext_abc123",
      "source": "onboarding-chat",
      "category": "person",
      "key": "name",
      "value": "Sarah",
      "confidence": 0.98,
      "createdAt": "2026-06-07T14:00:00Z"
    },
    {
      "id": "ext_def456",
      "source": "onboarding-chat",
      "category": "financials",
      "key": "mrr",
      "value": "40000",
      "confidence": 0.91,
      "createdAt": "2026-06-07T14:00:00Z"
    }
  ],
  "count": 2
}
# All facts
curl http://localhost:3001/v1/ai/extracted-data \
  -H "x-api-key: sk_live_xxx"

# Filter by source and category
curl "http://localhost:3001/v1/ai/extracted-data?source=onboarding-chat&category=financials" \
  -H "x-api-key: sk_live_xxx"

Retrieve a single extracted fact by its ID.

Path / Query parameters
NameTypeRequiredDescription
idstringrequiredExtracted fact ID
Response
{
  "data": {
    "id": "ext_abc123",
    "source": "onboarding-chat",
    "category": "person",
    "key": "name",
    "value": "Sarah",
    "confidence": 0.98,
    "createdAt": "2026-06-07T14:00:00Z"
  }
}

Permanently delete an extracted fact. Use this to correct a misidentified extraction or enforce a right-to-erasure request.

Path / Query parameters
NameTypeRequiredDescription
idstringrequiredExtracted fact ID
Response
204 No Content