API Reference · v1.1

Build against the Personize API v1.1 with endpoint-level detail.

187 public v1.1 routes across memory, prompts, governance, agents, and key management. v1.1 collapses 37+ overlapping v1 routes into ~30 routes organized around three verbs (save, retrieve, manage) plus import for ETL. v1 is deprecated and still supported; see the migration guide.

Quickstart

Base URL: https://agent.personize.ai

curl -X GET "https://agent.personize.ai/api/v1.1/me" \
  -H "Authorization: Bearer sk_live_YOUR_KEY"

Most routes use `Authorization: Bearer sk_live_...` secret-key auth.

This is the v1.1 reference, the version to build against for all new integrations. v1 is deprecated but still works for existing integrations; it is documented separately at /docs/api/v1 (API v1 Reference (Deprecated)). No retirement date has been set yet.

SDK-backed routes are labeled so you can map each endpoint to the latest client method quickly.

Base URL

https://agent.personize.ai

Authentication

Secret keys for product routes, JWT for admin routes.

Error shape

{
  "success": false,
  "error": "rate_limit_exceeded",
  "message": "Monthly API limit exceeded.",
  "limit": 10000,
  "current": 10000
}

2 endpoints

Identity

Validate your key and inspect the organization, user, and plan attached to the current request context.

GET/api/v1.1/meSecret keySDK

Current context

Returns the current org, user, active key metadata, and plan limits.

Usage: Use this to confirm key scope and inspect plan limits before you build rate-limit handling.

SDK: client.me()

Response

{
  "success": true,
  "data": {
    "organization": { "id": "org_123" },
    "user": { "id": "user_123" },
    "key": { "id": "key_123", "scope": "admin" },
    "plan": {
      "name": "Free Plan",
      "limits": {
        "maxApiCallsPerMonth": 10000,
        "maxApiCallsPerMinute": 60
      }
    }
  }
}

SDK Example

const result = await client.me();
console.log(result.data.plan.limits);
  • Returns `429` when the current key has crossed per-minute or monthly limits.
GET/api/v1.1/me/entitlementsSecret keySDK

Plan entitlements

Returns the resolved plan entitlements for the current organization: limits, feature flags, BYOK policy, and per-operation tier availability.

Usage: Use this to drive client-side feature gating (for example, hiding BYOK config UI when `byok` is `disabled`) without hardcoding plan names.

SDK: client.entitlements()

Response

{
  "success": true,
  "data": {
    "planName": "Free Plan",
    "limits": {
      "maxOrganizations": 1,
      "maxAiAutomations": 2,
      "maxRecordsSyncIn": 1000,
      "maxRecordsSyncOut": 1000,
      "maxApiCallsPerMonth": 20000,
      "maxApiCallsPerMinute": 160,
      "maxActiveSchedules": 5,
      "maxSchedulesPerRecord": 2,
      "maxRetrievesPerMonth": 50000,
      "maxPromptsPerMonth": 2000
    },
    "features": ["basic_analytics", "standard_support"],
    "byok": "optional",
    "operationTiers": {
      "memorize": ["basic", "pro", "pro_fast", "ultra"],
      "generate": ["basic", "pro", "ultra"],
      "retrieval": ["fast", "deep"]
    }
  }
}

SDK Example

const result = await client.entitlements();
console.log(result.data.byok, result.data.operationTiers);
  • No extra DB call -- the plan is already resolved by the API key auth middleware.
  • `byok` is `disabled` | `optional` | `required`; values shown above reflect the Free Plan and vary by plan.

4 endpoints

AI And Async

Run prompt workflows, route messages through semantic guidelines, and poll async jobs when a call completes in the background.

POST/api/v1.1/promptSecret keySDK

Prompt execution

Runs a direct prompt or multi-step instruction chain with optional structured outputs, evaluation, auto-memorize, and attachments.

Usage: Use this for general-purpose generation, extraction, or tool-augmented reasoning from the public API.

SDK: client.ai.prompt() / client.ai.promptStream()

Parameters

FieldTypeWhereRequiredDescription
promptstringbodyNoSingle-step prompt text.
instructionsInstruction[]bodyNoOrdered instruction blocks for multi-step runs.
streambooleanbodyNoSet `true` for SSE streaming; `false` returns an async event or sync result.
tierstringbodyNoQuality and pricing tier.Default: proOptions: basic, pro, ultra
attachmentsAttachment[]bodyNoImages, PDFs, or documents sent with the prompt.
outputsOutputDefinition[]bodyNoNamed structured outputs extracted server-side.

Request

{
  "prompt": "Summarize our Q4 sales strategy in five bullets",
  "tier": "pro",
  "outputs": [{ "name": "summary" }]
}

Response

{
  "success": true,
  "text": "Here is the finished draft.",
  "outputs": { "summary": "..." },
  "metadata": {
    "model": "anthropic/claude-sonnet-4-20250514",
    "provider": "anthropic",
    "tier": "pro",
    "creditsCharged": 1,
    "usage": {
      "promptTokens": 120,
      "completionTokens": 450
    },
    "toolCalls": [],
    "stepsExecuted": 1,
    "instructionsExecuted": 1
  }
}

SDK Example

const result = await client.ai.prompt({
  prompt: "Summarize our Q4 sales strategy in five bullets",
  tier: "pro",
  outputs: [{ name: "summary" }],
});
console.log(result.outputs.summary);
  • When `stream` is `false`, some requests complete asynchronously and return an event you can poll.
  • For SSE, use the SDK streaming helper rather than building the parser by hand.
GET/api/v1.1/eventsSecret key

List async events

Lists async events for the current organization.

Usage: Use this to inspect recent prompt, batch memorization, or evaluation jobs.

Parameters

FieldTypeWhereRequiredDescription
limitnumberqueryNoPage size.Default: 20
nextTokenstringqueryNoCursor from a previous page.

Response

{
  "success": true,
  "data": {
    "events": [
      { "eventId": "evt_123", "status": "processing", "type": "prompt" }
    ],
    "count": 1,
    "nextToken": "eyJ..."
  }
}
GET/api/v1.1/events/:idSecret key

Get async event

Fetches the current state of one async event.

Usage: Use this to poll prompt, batch, or evaluation jobs until they finish.

Parameters

FieldTypeWhereRequiredDescription
idstringpathYesThe event ID returned by an async endpoint.

Response

{
  "success": true,
  "data": {
    "eventId": "evt_123",
    "status": "completed",
    "responsePayload": { "success": true }
  }
}
GET/api/v1.1/events/:id/detailsSecret key

Get event details

Returns subrecords or detail rows for a large async job.

Usage: Use this when you need item-level detail from a batch process.

Parameters

FieldTypeWhereRequiredDescription
idstringpathYesEvent ID.
limitnumberqueryNoPage size for detail rows.
nextTokenstringqueryNoCursor for more detail rows.

Response

{
  "success": true,
  "data": {
    "eventId": "evt_123",
    "details": [],
    "count": 0
  }
}

2 endpoints

AI Generation

Step-driven AI orchestration and OpenAI-compatible chat completions with built-in governance, memory, and client-executed tools.

POST/api/v1.1/responsesSecret keySDK

Create a response

Execute step-driven AI orchestration with governance, memory, and client-executed tools. The primary generation endpoint.

Usage: Run multi-step AI workflows with fine-grained control over tool access, governance injection, and structured output extraction.

SDK: client.responses.create(options)

Parameters

FieldTypeWhereRequiredDescription
stepsStepDefinition[]bodyNoOrdered instructions for the AI to execute sequentially. Each step has a prompt, optional tool scope, and max_steps.
messagesChatMessage[]bodyNoAlternative to steps. Standard message array (system/user/assistant). Converted to a single step internally.
toolsClientToolSchema[]bodyNoUser-defined tools (JSON schema only). When the LLM calls these, the response returns requires_action and the SDK executes them on your server.
tierstringbodyNoModel quality tier when not using BYOK.Default: basicOptions: basic, pro, ultra
modelstringbodyNoModel ID for BYOK mode (e.g., 'claude-sonnet-4-6', 'gpt-4o').
providerstringbodyNoProvider for BYOK mode.Options: openrouter, openai, anthropic, google, deepseek, xai
llm_api_keystringbodyNoYour own LLM provider API key for BYOK mode.
session_idstringbodyNoGroup related requests into a session for SmartGuidelines deduplication.
temperaturenumberbodyNoSampling temperature (0-2).Range: 02
max_tokensnumberbodyNoMaximum tokens for the response.

Request

{
  "tier": "pro",
  "steps": [
    { "prompt": "Research {{company}} and find their tech stack", "tools": ["web_search"] },
    { "prompt": "Draft a personalized outreach email" }
  ],
  "personize": {
    "governance": { "guideline_ids": ["brand-voice"] },
    "memory": { "record_id": "contact-123", "recall": true },
    "outputs": [
      { "key": "email_subject", "type": "string" },
      { "key": "email_body", "type": "string" }
    ]
  }
}

Response

{
  "id": "resp_abc123",
  "status": "completed",
  "session_id": "sess_xyz789",
  "output": [{
    "type": "message",
    "role": "assistant",
    "content": [{ "type": "text", "text": "Here's the drafted email..." }]
  }],
  "outputs": {
    "email_subject": "Quick question about your React stack",
    "email_body": "Hi Sarah, I noticed Acme Corp..."
  },
  "usage": { "prompt_tokens": 2000, "completion_tokens": 800, "total_tokens": 2800 },
  "metadata": { "tier": "pro", "credits_charged": 3.2 }
}

SDK Example

const result = await client.responses.create({
  tier: 'pro',
  steps: [
    { prompt: 'Research {{company}} tech stack', tools: ['web_search'] },
    { prompt: 'Draft a personalized outreach email' }
  ],
  personize: {
    governance: { guideline_ids: ['brand-voice'] },
    memory: { record_id: 'contact-123', recall: true }
  }
});
console.log(result.outputs.email_subject);
  • If both steps and messages are provided, steps takes priority.
  • Client-executed tools: define tool schemas in the request. When the LLM calls them, the response returns status: 'requires_action'. Your SDK executes the tool locally and sends the result back.
  • BYOK mode: provide llm_api_key + model + provider to use your own LLM key. Billed at a flat platform fee instead of per-token.
  • Session IDs enable SmartGuidelines deduplication across requests in the same session.
POST/api/v1.1/chat/completionsSecret keySDK

Chat completion (OpenAI-compatible)

OpenAI-compatible chat completion endpoint. Drop-in replacement that adds Personize governance, memory, and evaluation on top of standard chat.

Usage: Quick integration for developers migrating from OpenAI. Supports all Personize extensions as optional parameters.

SDK: client.chat.completions.create(options)

Parameters

FieldTypeWhereRequiredDescription
messagesChatMessage[]bodyYesStandard OpenAI message array (system/user/assistant/tool roles).
toolsToolSchema[]bodyNoOpenAI-format tool definitions. Client-executed tools work the same as /responses.
tool_choicestringbodyNoTool selection mode.Options: auto, none, required
tierstringbodyNoModel quality tier.Default: basicOptions: basic, pro, ultra
modelstringbodyNoModel ID for BYOK mode.
providerstringbodyNoProvider for BYOK mode.Options: openrouter, openai, anthropic, google, deepseek, xai
llm_api_keystringbodyNoYour own LLM provider API key.
temperaturenumberbodyNoSampling temperature (0-2).Range: 02
max_tokensnumberbodyNoMaximum tokens for the response.

Request

{
  "messages": [
    { "role": "system", "content": "You are a helpful assistant." },
    { "role": "user", "content": "Draft a follow-up email for Acme Corp" }
  ],
  "tier": "pro",
  "personize": {
    "governance": { "guideline_ids": ["brand-voice"] },
    "memory": { "record_id": "contact-123", "recall": true }
  }
}

Response

{
  "id": "chatcmpl-abc123",
  "object": "chat.completion",
  "created": 1710892800,
  "model": "google/gemini-2.5-flash-lite-preview-09-2025",
  "choices": [{
    "index": 0,
    "message": {
      "role": "assistant",
      "content": "Subject: Following up on our conversation..."
    },
    "finish_reason": "stop"
  }],
  "usage": { "prompt_tokens": 1200, "completion_tokens": 450, "total_tokens": 1650 },
  "session_id": "sess_xyz789",
  "metadata": { "tier": "pro", "credits_charged": 1.5 }
}

SDK Example

const chat = await client.chat.completions.create({
  messages: [
    { role: 'user', content: 'Draft a follow-up email for Acme Corp' }
  ],
  personize: {
    governance: { guideline_ids: ['brand-voice'] },
    memory: { record_id: 'contact-123', recall: true }
  }
});
console.log(chat.choices[0].message.content);
  • Response format matches the OpenAI Chat Completions API exactly.
  • Personize extensions (governance, memory) are optional. Without them, this behaves like a standard chat completion.
  • Internally converts messages to a single-step orchestration call.

7 endpoints

Memory

Store, recall, filter, and summarize customer memory across structured fields and free-form context.

POST/api/v1.1/memory/saveSecret keySDK

Save memory (shape: shortform | document)

Single-record AI-powered write. The `shape` discriminator selects between extraction-driven `shortform` (default) and typed `document` storage. Subsumes v1's `/memorize`, `/memorize_pro`, `/memorize_context`, and `/context/save`. For structured key/value writes without AI extraction use `POST /api/v1.1/memory/upsert`.

Usage: The one write endpoint for AI-powered single saves. `shape='shortform'` runs extraction to produce atoms + properties. `shape='document'` stores typed guidelines/playbooks/references with optional metadata enrich.

SDK: client.v1_1.memory.save()

Parameters

FieldTypeWhereRequiredDescription
contentstringbodyYesThe source content to store.
shapestringbodyNoWhat data shape is being sent. Defaults to `shortform`. Use `document` for typed knowledge docs.Default: shortformOptions: shortform, document
typestringbodyNoRequired when `shape='document'`. Registry value from `/context/manage/doc-types` (e.g. `guideline`, `playbook`, `reference`, `template`, `brief`, or org-defined).
recordIdstringbodyNoPresent = record-scoped write. Absent = org-scoped.
propertiesobjectbodyNoSeed/hints for `shortform` (LLM may extract both atoms + properties); metadata for `document`.
optionsobjectbodyNoPer-write controls: `mode` (sync/async/bulk), `tier`, `upsert`, `enrich`, `aiGenerated`, `collectionIds`.

Request

{
  "shape": "shortform",
  "content": "Meeting notes: John prefers email and is evaluating in Q2.",
  "properties": { "email": "john@example.com" },
  "options": { "tier": "pro" },
  "collectionGraph": true,
  "smartGraph": true,
  "relations": [
    { "relationType": "works_at",
      "toIdentity": { "kind": "websiteUrl", "value": "acme.com" },
      "toEntityType": "company" }
  ]
}

Response

{
  "shape": "shortform",
  "recordId": "rec_xyz",
  "memories": [...],
  "properties": {...},
  "destination": "fast-sf",
  "reason": "fast-eligible",
  "jobId": "evt_...",
  "estimatedCompletionMs": 12000,
  "cost": { "credits": 3, "tier": "pro" },
  "warnings": [],
  "deduplication": { "skipped": 0, "merged": 0 }
}

SDK Example

const result = await client.v1_1.memory.save({
  content: "John prefers email and is evaluating in Q2.",
  properties: { email: "john@example.com" },
  // shape defaults to 'shortform' (extracts both atoms + properties)
});

// Document shape (stores a typed guideline)
const doc = await client.v1_1.memory.save({
  shape: "document",
  type: "guideline",
  content: "## Sales Playbook\n...",
  options: { enrich: true },
});
  • Two credit tiers: full extraction / metadata enrich. Tier billed is surfaced in response.cost.tier.
  • `recordId` carries scope: present = record-scoped, absent = org-scoped.
  • Aliases: `/memorize`, `/memorize_pro`, `/upsert`, `/context/save` are accepted but deprecated and emit RFC 8594 Deprecation headers.
  • For structured key/value writes without AI extraction (CRM fields, direct property sets), use `POST /api/v1.1/memory/upsert`.
POST/api/v1.1/memory/upsertSecret keySDK

Upsert entity properties (no AI)

Structured create-or-merge write: directly sets known field values on a collection row without running AI extraction. Use this when you have exact property values (CRM fields, form submissions, ETL rows) and no free text to extract from. For free-text content that needs LLM extraction use `POST /api/v1.1/memory/save`. For CSV or CRM bulk imports with a field mapping use `POST /api/v1.1/memory/import`.

Usage: The no-AI write endpoint. Directly merges a `properties` map into the target collection row for the identified entity. Returns HTTP 202 immediately; the write is queued and async. Supports single-record and batch (items[]) payloads.

SDK: client.v1_1.memory.upsert()

Parameters

FieldTypeWhereRequiredDescription
emailstringbodyNoIdentity: contact email address. Provide exactly one of `email`, `recordId`, `websiteUrl`, or `customKey`+`customKeyName`.
recordIdstringbodyNoIdentity: existing record ID. Alternative to `email`/`websiteUrl`/`customKey`.
websiteUrlstringbodyNoIdentity: company or person website URL. Alternative to `email`/`recordId`/`customKey`.
customKeystringbodyNoIdentity: arbitrary string key. Must be paired with `customKeyName`.
customKeyNamestringbodyNoName of the custom key namespace. Required when `customKey` is supplied.
typestringbodyYesEntity type (e.g. `contact`, `company`, `deal`). Must match a registered entity type in the org.
collectionNamestringbodyNoTarget collection name. Resolved to a collection ID server-side. Use either `collectionName` or `collectionId`, not both. The collection must already exist.
collectionIdstringbodyNoTarget collection ID. Alternative to `collectionName`. The collection must already exist.
propertiesobjectbodyNoKey/value map of properties to write. Values must be scalars: strings, or numbers/booleans that are auto-converted to strings (e.g. 5 becomes "5", true becomes "true"). Objects and arrays are rejected with 400.
itemsobject[]bodyNoBatch mode: array of per-record upsert payloads. Each item follows the single-record body shape. Max 100 items. Cannot be combined with top-level `properties` or identity fields.

Request

{
  "email": "jane@example.com",
  "type": "contact",
  "collectionName": "contacts",
  "properties": {
    "first_name": "Jane",
    "company": "Acme Corp",
    "deal_stage": "Proposal",
    "deal_value": "50000"
  }
}

// Batch variant
{
  "items": [
    {
      "email": "jane@example.com",
      "type": "contact",
      "collectionName": "contacts",
      "properties": { "deal_stage": "Closed Won", "deal_value": "75000" }
    },
    {
      "websiteUrl": "acme.com",
      "type": "company",
      "collectionName": "companies",
      "properties": { "industry": "Technology", "employee_count": "500" }
    }
  ]
}

Response

{
  "data": {
    "eventId": "evt_01j...",
    "jobId": "job_01j...",
    "status": "queued"
  }
}

SDK Example

// Single record
const result = await client.v1_1.memory.upsert({
  email: "jane@example.com",
  type: "contact",
  collectionName: "contacts",
  properties: {
    first_name: "Jane",
    company: "Acme Corp",
    deal_stage: "Proposal",
    deal_value: "50000",
  },
});
// Returns HTTP 202 immediately; poll jobId for completion

// Batch
const batch = await client.v1_1.memory.upsert({
  items: [
    { email: "jane@example.com", type: "contact", collectionName: "contacts", properties: { deal_stage: "Closed Won" } },
    { websiteUrl: "acme.com", type: "company", collectionName: "companies", properties: { industry: "Technology" } },
  ],
});
  • No AI extraction runs. This is a direct write path billed at the structured-write (no-AI) rate.
  • The target collection must already exist. Use `POST /api/v1.1/context/manage` to create collections.
  • Identity is required: supply exactly one of `email`, `recordId`, `websiteUrl`, or `customKey`+`customKeyName`.
  • Write semantics are create-or-merge: existing property values are overwritten by the supplied map; unmentioned keys are left unchanged.
  • `options.upsert: false` is not supported on this endpoint; the create-or-merge behavior is always on.
  • Scalar numbers and booleans are auto-converted to strings. Objects or arrays in `properties` values are rejected with 400.
  • Returns HTTP 202 immediately. The write is queued; poll the returned `jobId` to confirm completion.
  • Succeeds v1's `POST /upsert` and the structured-properties path of `POST /batch-memorize`.
POST/api/v1.1/memory/save/batchSecret keySDK

Save memory batch (per-item shape)

N independent saves in one call. Per-item `shape`. Loop-dispatch handler (MAX_BATCH_SIZE=100). Returns 207 Multi-Status on partial failures.

Usage: Use when you have a small heterogeneous set of saves that should run together but may have different shapes per item. For homogeneous, schema-driven imports use `/memory/import` instead.

SDK: client.v1_1.memory.saveBatch()

Parameters

FieldTypeWhereRequiredDescription
itemsobject[]bodyYesArray of per-item save payloads. Each item follows the `/memory/save` body shape. Max 100 items per call.
optionsobjectbodyNoTop-level defaults applied to each item (overridable per item). `mode` must be `async` or `bulk` for batches — sync not allowed.

Request

{
  "items": [
    { "shape": "shortform", "content": "...", "recordId": "rec_1" },
    { "shape": "document", "type": "guideline", "content": "..." }
  ],
  "options": { "mode": "async", "tier": "pro" }
}

Response

{
  "results": [
    { "ok": true, "shape": "shortform", "recordId": "rec_1", "jobId": "evt_1" },
    { "ok": false, "error": "schema_violation", "message": "missing required type for shape=document" }
  ],
  "summary": { "total": 2, "succeeded": 1, "failed": 1 }
}
  • Returns 207 Multi-Status when any item fails; 200 when all succeed.
  • Per-item `options` override top-level `options` for that item only.
  • Server routes individual items to `fast-sf` (<25 records) or `bulk-sf` (≥25, batch-capable provider).
POST/api/v1.1/memory/importSecret keySDK

Import memory (ETL with per-property extract)

Bulk ETL ingestion: mapping + rows with per-property `extract` flag. Public promotion of the internal CRM-sync `batch-memorize-import` path — same payload schema, same per-field AI-routing.

Usage: Use for CRM/warehouse/spreadsheet imports where each property has known direct-write vs. LLM-extract handling. The per-property `extract: true` flag opts a field into AI extraction; default `false` writes directly.

SDK: client.v1_1.memory.import()

Parameters

FieldTypeWhereRequiredDescription
sourcestringbodyYesOrigin label.Options: hubspot, salesforce, apollo, nango, api-custom
mappingobjectbodyYes`{ entityType, propertyMappings: [{ source, target, extract, aiGenerated, direction }] }`.
rowsobject[]bodyYesRaw tabular rows keyed by provider field names.
optionsobjectbodyNo`{ mode: 'async'|'bulk', tier, dryRun, chunkSize }`.

Request

{
  "source": "hubspot",
  "mapping": {
    "entityType": "contact",
    "propertyMappings": [
      { "source": "firstname", "target": { "propertyName": "first_name" }, "extract": false, "direction": "both" },
      { "source": "notes", "target": { "propertyName": "notes" }, "extract": true, "direction": "in" }
    ]
  },
  "rows": [
    { "firstname": "John", "notes": "Long transcript..." }
  ],
  "options": { "mode": "async", "tier": "pro" }
}

Response

{
  "jobId": "evt_...",
  "destination": "bulk-sf",
  "enqueuedRows": 1500,
  "chunks": 15,
  "estimatedCompletionMs": 45000,
  "cost": {
    "estimatedCredits": 450,
    "tier": "pro",
    "perPropertyEstimate": {
      "notes": { "credits": 450, "extract": true },
      "firstname": { "credits": 0, "extract": false }
    }
  }
}
  • Per-property `extract: true` runs LLM extraction on that field; `extract: false` (default) writes the value directly.
  • `direction: 'in' | 'out' | 'both'` controls sync semantics.
  • Use `options.dryRun: true` for a no-op validation pass with cost estimate.
POST/api/v1.1/memory/retrieveSecret keySDK

Retrieve memory (5 modes)

Unified retrieve with 5 modes: `scout` (default, multi-source no-LLM), `brief` (LLM synthesis), `expand` (paginate), `filter` (17 operators), `fetch` (by IDs). Zero schema change from v1 `/retrieve`, `/smart-recall*`, `/recall*`, `/search`, `/smart-memory-digest`, `/similar`, `/filter-by-property`.

Usage: The single retrieve endpoint. Pick a `mode` based on intent: scout for fast recall, brief for synth, expand for pagination, filter for structured property queries, fetch for known-ID lookups.

SDK: client.v1_1.memory.retrieve()

Parameters

FieldTypeWhereRequiredDescription
messagestringbodyNoNatural language query. Required for `scout`, `brief`, `expand`.
modestringbodyNoRetrieval mode.Default: scoutOptions: scout, brief, expand, filter, fetch
identifiersobjectbodyNo`{ emails?, websites?, record_ids? }` — scope retrieval to known entities.
filtersobjectbodyNoRequired for `mode='filter'`. Structured property filters with 17 operators.
idsstring[]bodyNoRequired for `mode='fetch'`. Direct memory/record IDs to load.
limitnumberbodyNoMax results.Default: 10Range: 15000
token_budgetnumberbodyNoToken budget for `brief` mode compiled context.Default: 4000
session_idstringbodyNoSession ID for follow-up queries.
output_formatstringbodyNoResponse shape.Default: structuredOptions: structured, narrative

Request

{
  "mode": "scout",
  "message": "What is John's budget and timeline?",
  "identifiers": { "emails": ["john@example.com"] }
}

Response

{
  "success": true,
  "mode": "scout",
  "data": {
    "results": [
      { "text": "John's budget is $50k, evaluating in Q2.", "score": 0.91, "record_id": "rec_456" }
    ],
    "creditsCharged": 1
  }
}

SDK Example

// Scout — fast no-LLM recall
const scout = await client.v1_1.memory.retrieve({
  mode: "scout",
  message: "AI automation buyers",
});

// Brief — LLM-synthesized answer
const brief = await client.v1_1.memory.retrieve({
  mode: "brief",
  message: "What is John's budget?",
  identifiers: { emails: ["john@example.com"] },
  token_budget: 2000,
});

// Filter — structured property query
const filter = await client.v1_1.memory.retrieve({
  mode: "filter",
  filters: { ARR: { gt: 10000 }, "Renewal Date": { exists: true } },
});
  • Replaces v1's `/smart-recall`, `/smart-recall-unified`, `/recall`, `/recall_pro`, `/search`, `/smart-memory-digest`, `/similar`, `/memory/filter-by-property`, `/memory/query-properties`.
  • `scout` ~ 1 credit. `brief` ~ 2 credits. `expand`/`filter`/`fetch` cheap (≤1 credit) when no LLM is in the loop.
  • Retrieved documents default to `status='active'` (widen via `statuses`). Each returned document carries `updatedAt` (ISO 8601 last-update) and `status` (`active`/`draft`/`retired`/`archived`) so agents can weigh staleness.
POST/api/v1.1/memory/retrieve/feedbackSecret keySDK

Record retrieve feedback

Records a thumbs-up/down (and optional notes) on a previous `POST /retrieve` call, keyed by its `retrievalId`.

Usage: Use this to capture end-user feedback on retrieve answers so you can track answer quality over time.

SDK: client.retrieveFeedback({ retrievalId, rating, notes })

Parameters

FieldTypeWhereRequiredDescription
retrievalIdstringbodyYesThe `retrievalId` returned by the earlier `POST /retrieve` call.
ratingnumberbodyYes1 = thumbs up, 0 = neutral, -1 = thumbs down.Options: -1, 0, 1
notesstringbodyNoOptional free-text notes, up to 2000 characters.

Request

{
  "retrievalId": "5f2c1e9a-0b1f-4a3d-9c2e-1a2b3c4d5e6f",
  "rating": 1,
  "notes": "Answer correctly cited the renewal date."
}

Response

{
  "feedbackId": "fb_...",
  "retrievalId": "5f2c1e9a-0b1f-4a3d-9c2e-1a2b3c4d5e6f"
}
  • Returns 404 for organizations that do not have unified retrieve (`POST /retrieve`) enabled -- feedback capture shares that endpoint's rollout gate.
GET/api/v1.1/memory/retrieve/:retrievalId/synthesisSecret keySDK

Poll deferred retrieve synthesis

Polls for the result of a deferred LLM synthesis started by a `POST /retrieve` call, when the synthesis was not returned inline.

Usage: Use this after a `POST /retrieve` response indicates a deferred synthesis, polling until `status` moves from `pending` to `ok` or `error`.

SDK: client.getSynthesis(retrievalId)

Parameters

FieldTypeWhereRequiredDescription
retrievalIdstringpathYesThe `retrievalId` returned by the earlier `POST /retrieve` call. Must be a UUID.

Response

{
  "retrievalId": "5f2c1e9a-0b1f-4a3d-9c2e-1a2b3c4d5e6f",
  "orgId": "org_123",
  "status": "ok",
  "answer": "John's budget is $50k, evaluating in Q2.",
  "confidence": "high",
  "sourcesUsed": ["rec_456"],
  "durationMs": 1840,
  "totalTokens": 612,
  "createdAt": 1771200000000,
  "completedAt": 1771200001840
}
  • `status` is `pending` (poll again), `ok` (answer/confidence/sourcesUsed populated), or `error` (`errorMessage` populated).
  • Returns 404 with `error: 'synthesis_not_found'` when no deferred synthesis row exists for that `retrievalId`, and 404 with `error: 'not_found'` for organizations without unified retrieve enabled.

15 endpoints

Memory Manage (REST verbs)

Deterministic CRUD on records, properties, and memories — no LLM, low cost. v1.1 collapses v1's action-named POSTs (`/memory/update`, `/memory/delete-record`, `/memory/property-history`, `/similar`) into REST verbs under `/memory/manage/*`.

GET/api/v1.1/memory/manageSecret keySDK

List records

Paginated list of records with optional filters (type, collection, owner, updatedAt range).

Usage: Browse all records in an org. For ad-hoc structured queries use `POST /memory/retrieve` mode=`filter`.

SDK: client.v1_1.memory.manage.list()

Parameters

FieldTypeWhereRequiredDescription
typestringqueryNoFilter by entity type.
collectionIdstringqueryNoFilter by collection.
limitnumberqueryNoPage size.Default: 50
nextTokenstringqueryNoCursor from previous page.

Response

{
  "data": { "records": [{ "recordId": "rec_1", "type": "contact", ... }], "nextToken": null }
}
GET/api/v1.1/memory/manage/:idSecret keySDK

Get one record

Runs a natural-language match over a record's stored property values using the same LLM matcher as v1's `POST /memory/query-properties`.

Usage: Use this to ask a plain-language question about a record's properties (for example, "which industry looks like fintech") instead of doing an exact-value filter.

SDK: client.v1_1.memory.manage.get(id)

Parameters

FieldTypeWhereRequiredDescription
idstringpathYesRecord ID segment for REST symmetry with the rest of `/memory/manage/*`. Not read by the current handler -- see notes.
propertyNamestringbodyYesProperty to search over.
querystringbodyYesNatural-language query the LLM matches property values against.
recordIdstringbodyNoRestrict the search to one record's property history. Set this to the same value as the `:id` in the URL (see notes).
typestringbodyNoEntity type to scan when `recordId` is omitted.Default: contact
limitnumberbodyNoMax property values sent to the LLM.Default: 100

Request

{
  "recordId": "rec_abc123",
  "propertyName": "industry",
  "query": "companies in fintech or banking"
}

Response

{
  "success": true,
  "data": {
    "matches": [
      { "recordId": "rec_abc123", "propertyValue": "Financial Services", "matchReason": "Financial Services is a fintech/banking industry" }
    ],
    "processedCount": 1,
    "totalAvailable": 1,
    "truncated": false
  }
}
  • The `:id` path segment scopes the call. If you also send `recordId` in the body it takes precedence, which keeps older v1-style callers working unchanged.
  • This is an LLM-powered match, not a plain record fetch. To read a record's stored properties directly, use `GET /memory/manage` (list, with filters) or `POST /memory/retrieve` with `mode: 'fetch'`.
PATCH/api/v1.1/memory/manage/:idSecret keySDK

Update record properties

Direct write of one property (or one freeform-memory entry) on a record -- deterministic, no LLM, 0 credits. Replaces v1's `POST /memory/update`.

Usage: Use this for a known single-property write when you don't need extraction.

SDK: client.v1_1.memory.manage.update(id, ...)

Parameters

FieldTypeWhereRequiredDescription
idstringpathYesRecord ID segment. Not read by the current handler -- see notes.
recordIdstringbodyYesRecord to update. Set this to the same value as the `:id` in the URL (see notes), or omit it and pass identifiers (`email`, `websiteUrl`, etc.) for the handler to resolve.
typestringbodyNoEntity type.Default: contact
propertyNamestringbodyNoProperty to write. Required together with `propertyValue` or one of the array-operation fields.
propertyValueanybodyNoNew value. Pass `null` to delete the property. Mutually exclusive with `arrayPush`/`arrayRemove`/`arrayPatch`.
arrayPushobjectbodyNo`{ items: any[], unique?: boolean }`. Append to an array property.
arrayRemoveobjectbodyNo`{ items?: any[], indices?: number[] }`. Remove entries from an array property.
arrayPatchobjectbodyNo`{ match: object, set: object }`. Patch array entries matching `match`.
memoryIdstringbodyNoEdit one freeform memory's text instead of a property. Requires `text` too; mutually exclusive with the property fields.
textstringbodyNoNew text for `memoryId`.
expectedVersionnumberbodyNoOptimistic-concurrency guard. Fails with 409 if the record's current version doesn't match.
reasonstringbodyNoFree-text audit note.
updatedBystringbodyNoAttribution for the change. Defaults to the caller's user ID.

Request

{
  "recordId": "rec_abc123",
  "type": "contact",
  "propertyName": "Lifecycle Stage",
  "propertyValue": "SQL"
}

Response

{
  "success": true,
  "data": {
    "success": true,
    "previousValue": "MQL",
    "newValue": "SQL",
    "version": 4,
    "stores": { "snapshot": "updated", "lancedb": "updated", "freeform": "updated" }
  }
}
  • The `:id` path segment scopes the call. If you also send `recordId` in the body it takes precedence, which keeps older v1-style callers working unchanged.
  • Returns 409 with `currentVersion` on an `expectedVersion` mismatch.
  • Returns 409 `org_not_set_up` if the org has no collections yet (no schema to write the property into).
PATCH/api/v1.1/memory/manageSecret keySDK

Bulk update one record's properties

Writes multiple properties on ONE record in a single call. Replaces v1's `POST /memory/bulk-update`.

Usage: Use for a batched direct write to several properties of the same record in one round trip.

SDK: client.v1_1.memory.manage.bulkUpdate()

Parameters

FieldTypeWhereRequiredDescription
recordIdstringbodyYesRecord to update, or omit and pass identifiers (`email`, `websiteUrl`, etc.) for the handler to resolve.
typestringbodyNoEntity type.Default: contact
updatesobject[]bodyYesArray of `{ propertyName, propertyValue, collectionId?, confidence? }`, one entry per property to write.
expectedVersionnumberbodyNoOptimistic-concurrency guard against the record's current version.
updatedBystringbodyNoAttribution for the change.

Request

{
  "recordId": "rec_abc123",
  "type": "contact",
  "updates": [
    { "propertyName": "Lifecycle Stage", "propertyValue": "SQL" },
    { "propertyName": "ARR", "propertyValue": 50000 }
  ]
}

Response

{
  "success": true,
  "data": {
    "success": true,
    "results": [
      { "propertyName": "Lifecycle Stage", "previousValue": "MQL", "newValue": "SQL", "status": "updated" },
      { "propertyName": "ARR", "previousValue": 25000, "newValue": 50000, "status": "updated" }
    ],
    "version": 5
  }
}
  • Despite the bare `/manage` path (no `:id`), this writes several properties on ONE record identified by `recordId`/identifiers in the body -- it does not update multiple records in one call.
DELETE/api/v1.1/memory/manage/:idSecret keySDK

Delete record

Soft-deletes a record by default (30-day recovery window via `pendingDeletion` + `deletionTTL`). Pass `?hard=true` for an irreversible hard delete that cascades across snapshot, vector store, and freeform memories. Replaces v1's `POST /memory/delete-record` (soft) and `POST /memory/delete-resource` (hard).

Usage: Remove a record, with or without the 30-day recovery grace period.

SDK: client.v1_1.memory.manage.delete(id, { hard })

Parameters

FieldTypeWhereRequiredDescription
idstringpathYesRecord ID. Read correctly on the `?hard=true` path; on the default soft-delete path it is NOT read -- see notes.
hardstringqueryNoSet to `true` for an irreversible hard delete. Omit for the default 30-day-recoverable soft delete.Options: true
typestringbodyNoEntity type of the record. Required on the default (soft) path only.
recordIdstringbodyNoRecord to delete. Required on the default (soft) path only -- set it to the same value as `:id` (see notes).
reasonstringbodyNoFree-text audit note. Accepted on both the soft and hard paths.

Request

{
  "type": "contact",
  "recordId": "rec_abc123",
  "reason": "duplicate"
}

Response

{
  "success": true,
  "receiptId": "del_xyz789",
  "hardDeleteAt": "2026-10-01T00:00:00.000Z",
  "deletedCounts": { "snapshot": 1, "lancedb": 3, "freeform": 3 },
  "errors": []
}
  • Default (soft) path does not read the `:id` path segment -- pass `type` and `recordId` in the JSON body, or the call returns 400.
  • `?hard=true` correctly scopes to the `:id` in the URL (via the same dispatcher as `POST /memory/manage/delete`) and needs no body beyond an optional `reason`.
  • Soft-deleted records are recoverable via `POST /manage/:id/restore` until `deletionTTL` fires (~30 days).
POST/api/v1.1/memory/manage/:id/restoreSecret keySDK

Restore a pending-deletion record

Cancels a pending soft-deletion within the 30-day grace window, restoring the record. Replaces v1's `POST /memory/cancel-deletion`.

Usage: Undo an accidental or premature soft delete before the hard-delete TTL fires.

SDK: client.v1_1.memory.manage.restore(id)

Parameters

FieldTypeWhereRequiredDescription
idstringpathYesRecord ID segment. Not read by the current handler -- see notes.
recordIdstringbodyYesRecord to restore. Set this to the same value as the `:id` in the URL (see notes).
typestringbodyNoEntity type.Default: contact

Request

{
  "recordId": "rec_abc123",
  "type": "contact"
}

Response

{
  "success": true,
  "data": {
    "success": true,
    "restoredCounts": { "snapshot": "restored", "freeform": "restored", "lancedb": "restored" }
  }
}
  • The `:id` path segment scopes the call. If you also send `recordId` in the body it takes precedence, which keeps older v1-style callers working unchanged.
  • Returns 409 (`DELETION_FINALIZED`) if the 30-day grace window already elapsed and the hard delete has run.
GET/api/v1.1/memory/manage/:id/similarSecret keySDK

Find similar to seed record

Lookalike search seeded by a record's stored property/memory vectors. Replaces v1's `POST /similar`.

Usage: Lookalike prospecting and relationship mapping from a known seed record.

SDK: client.v1_1.memory.manage.similar(id, params)

Parameters

FieldTypeWhereRequiredDescription
idstringpathYesSeed record ID segment. Not read by the current handler -- see notes.
seedobjectbodyYesIdentifies the seed record: `{ recordId }`, `{ email }`, `{ websiteUrl }`, or `{ customKeyName, customKeyValue }`. Set `seed.recordId` to the same value as `:id` (see notes).
typestringbodyNoSeed record's entity type.Default: contact
dimensionsstringbodyNoWhat drives similarity.Default: hybridOptions: properties, memories, hybrid, connections
topKnumberbodyNoNumber of neighbors to return.Default: 25
rankingModestringbodyNoHow dimension scores combine.Default: balancedOptions: balanced, weighted
weightsobjectbodyNo`{ properties, memories }`. Only used when `rankingMode` is `weighted`.
minScorenumberbodyNoDrop results below this score.
includeTiersbooleanbodyNoInclude a `tiers` map of recordId lists grouped by similarity tier.
returnAllIdsbooleanbodyNoReturn a flat `allIds` list with scores instead of the default per-result breakdown.

Request

{
  "seed": { "recordId": "rec_abc123" },
  "type": "contact",
  "dimensions": "hybrid",
  "topK": 25
}

Response

{
  "success": true,
  "seed": { "recordId": "rec_abc123", "type": "contact", "label": "rec_abc123", "vectorsUsed": 6 },
  "results": [
    {
      "recordId": "rec_def456",
      "type": "contact",
      "score": 0.82,
      "tier": "very_similar",
      "matchBreakdown": { "propertiesScore": 0.9, "memoriesScore": 0.7, "connectionsScore": null, "matchedProperties": [], "matchedMemorySnippets": [], "sharedConnections": null }
    }
  ],
  "totalMatches": 1,
  "metadata": { "seedVectorsUsed": 6, "totalCandidatesScanned": 340, "dimensions": "hybrid", "rankingMode": "balanced" },
  "usage": { "credits": 2 }
}
  • The `:id` path segment scopes the call. If you also send `recordId` in the body it takes precedence, which keeps older v1-style callers working unchanged.
  • Charges credits per call; the charge is skipped when the org has no records yet.
GET/api/v1.1/memory/manage/:id/property-historySecret keySDK

Property change history

Audit log of property changes for one record. Replaces v1's `POST /memory/property-history`.

Usage: Compliance review, sync-conflict diagnosis, drift detection.

SDK: client.v1_1.memory.manage.propertyHistory(id, params)

Parameters

FieldTypeWhereRequiredDescription
idstringpathYesRecord ID segment. Not read by the current handler -- see notes.
recordIdstringbodyYesRecord to look up. Set this to the same value as the `:id` in the URL (see notes).
propertyNamestringbodyNoFilter to one property. Omit to return history for all properties.
fromstringbodyNoISO-8601 start of the date range.
tostringbodyNoISO-8601 end of the date range.
limitnumberbodyNoPage size.Default: 50
nextTokenstringbodyNoCursor from a previous page.

Request

{
  "recordId": "rec_abc123",
  "propertyName": "Lifecycle Stage"
}

Response

{
  "success": true,
  "data": {
    "entries": [
      { "entryId": "mem_1", "propertyName": "Lifecycle Stage", "propertyValue": "SQL", "collectionId": "col_contact", "collectionName": "Contacts", "updatedBy": "user_123", "createdAt": "2026-08-20T10:00:00.000Z", "source": "extraction" }
    ],
    "nextToken": null
  }
}
  • The `:id` path segment scopes the call. If you also send `recordId` in the body it takes precedence, which keeps older v1-style callers working unchanged.
GET/api/v1.1/memory/manage/pending-deletionsSecret keySDK

List pending-deletion records

Returns records currently in the soft-delete grace window with their `deletionTTL` timestamps.

Usage: Admin/compliance UI for reviewing or reversing pending deletions before the TTL fires.

SDK: client.v1_1.memory.manage.pendingDeletions(params)

Parameters

FieldTypeWhereRequiredDescription
typestringbodyNoFilter to one entity type.
limitnumberbodyNoPage size.Default: 50
nextTokenstringbodyNoCursor from a previous page.

Response

{
  "success": true,
  "data": {
    "records": [
      { "recordId": "rec_abc123", "type": "contact", "pendingDeletion": true, "pendingDeletionAt": "2026-08-01T00:00:00.000Z", "deletionTTL": 1785000000, "hardDeleteAtEstimated": "2026-08-31T00:00:00.000Z", "email": "jane@example.com" }
    ],
    "nextToken": null
  }
}
  • Filters (`type`, `limit`, `nextToken`) are read from the JSON request body, not the query string, even though this is a GET route.
GET/api/v1.1/memory/manage/keysSecret keySDK

List CRM keys

List all CRM key aliases (standard + custom) registered for one record. Replaces v1's `POST /list-keys`.

Usage: Inspect what identifier types (email, websiteUrl, recordId, custom) have been registered for a record.

SDK: client.v1_1.memory.manage.keys.list()

Parameters

FieldTypeWhereRequiredDescription
typestringbodyYesEntity type of the record.
recordIdstringbodyNoRecord to look up. At least one lookup field is required.
emailstringbodyNoAlternative lookup field.
websiteUrlstringbodyNoAlternative lookup field.
phoneNumberstringbodyNoAlternative lookup field.
customKeyNamestringbodyNoCustom key kind, used together with `customKeyValue`.
customKeyValuestringbodyNoCustom key value, used together with `customKeyName`.

Response

{
  "success": true,
  "recordId": "rec_abc123",
  "keys": [
    { "kind": "email", "value": "jane@example.com", "standard": true, "createdAt": "2026-01-01T00:00:00.000Z", "lastSeenAt": "2026-08-20T00:00:00.000Z" },
    { "kind": "salesforceId", "value": "003xx000004tld9", "standard": false, "createdAt": "2026-02-10T00:00:00.000Z", "lastSeenAt": "2026-08-20T00:00:00.000Z" }
  ]
}
  • This is a per-record lookup, not an org-wide key listing -- `type` plus at least one lookup field (`recordId`, `email`, `websiteUrl`, `phoneNumber`, `postalCode`, `deviceId`, `contentId`, or `customKeyName`+`customKeyValue`) are required.
  • Filters are read from the JSON request body, not the query string, even though this is a GET route.
PATCH/api/v1.1/memory/manage/keysSecret keySDK

Bulk register CRM keys

Registers CRM key aliases across multiple records in one call. Replaces v1's `POST /update-keys-batch`.

Usage: Bulk-attach CRM identifiers (Salesforce ID, HubSpot ID, custom keys) to existing records, for example during a CRM sync.

SDK: client.v1_1.memory.manage.keys.bulkUpdate(body)

Parameters

FieldTypeWhereRequiredDescription
recordsobject[]bodyYesUp to 100 entries. Each needs `type`, a `keys` object (kind → value, max 50 entries), and at least one lookup field (`recordId`, `email`, `websiteUrl`, etc.) to resolve the record.

Request

{
  "records": [
    { "type": "contact", "email": "jane@example.com", "keys": { "salesforceId": "003xx000004tld9" } }
  ]
}

Response

{
  "success": true,
  "results": [
    { "recordId": "rec_abc123", "registered": 1, "conflicts": [] }
  ],
  "notFound": []
}
  • Max 100 records per batch request, max 50 keys per record.
  • Entries that fail to resolve to a record are returned in `notFound` rather than failing the whole batch.
PATCH/api/v1.1/memory/manage/keys/:keyNameSecret keySDK

Register CRM key(s) on a record

Registers one or more CRM key aliases on a record. Replaces v1's `POST /update-keys`.

Usage: Attach a CRM identifier (Salesforce ID, HubSpot ID, custom key) to an existing record.

SDK: client.v1_1.memory.manage.keys.update(keyName, body)

Parameters

FieldTypeWhereRequiredDescription
keyNamestringpathYesPresent in the URL for REST symmetry. Not read by the current handler -- the `keys` object in the body determines what gets written (see notes).
typestringbodyYesEntity type of the record.
keysobjectbodyYesMap of key kind to value, e.g. `{ salesforceId: '003xx' }`. Max 50 entries.
recordIdstringbodyNoRecord to update. At least one lookup field is required.
emailstringbodyNoAlternative lookup field.
websiteUrlstringbodyNoAlternative lookup field.

Request

{
  "type": "contact",
  "email": "jane@example.com",
  "keys": { "salesforceId": "003xx000004tld9" }
}

Response

{
  "success": true,
  "recordId": "rec_abc123",
  "registered": 1,
  "conflicts": []
}
  • The `:keyName` path segment is not yet read by this handler: pass the key in the body. This one was not bridged when the record routes were, because the handler takes a `keys` collection rather than a single name.
  • Returns 409 with `blockedReason` if the record is blocked from further key writes; individual key conflicts (value already registered to another record) are returned in `conflicts` rather than failing the request.
DELETE/api/v1.1/memory/manage/keys/:keyNameSecret keySDK

Delete CRM key(s) from a record

Removes one or more CRM key aliases from a record. Replaces v1's `POST /delete-keys`.

Usage: Remove a stale or incorrect CRM identifier from a record.

SDK: client.v1_1.memory.manage.keys.delete(keyName, body)

Parameters

FieldTypeWhereRequiredDescription
keyNamestringpathYesPresent in the URL for REST symmetry. Not read by the current handler -- pass the exact key(s) to remove in `keys` (see notes).
typestringbodyYesEntity type of the record.
keysobject[]bodyYesArray of `{ keyName, keyValue }` pairs to remove.
recordIdstringbodyNoRecord to update. At least one lookup field is required.

Request

{
  "type": "contact",
  "recordId": "rec_abc123",
  "keys": [{ "keyName": "salesforceId", "keyValue": "003xx000004tld9" }]
}

Response

{
  "success": true,
  "recordId": "rec_abc123",
  "deleted": 1,
  "notFound": 0
}
  • The `:keyName` path segment is not yet read by this handler: pass the key in the body. This one was not bridged when the record routes were, because the handler takes a `keys` collection rather than a single name.
  • Blocks deletion (409, entries returned in `blocked`) if removing a key would leave the record with zero remaining aliases.
DELETE/api/v1.1/memory/edges/:edgeIdSecret keySDK

Delete a graph edge

Hard-deletes one graph edge (relationship) by ID. Record-agnostic -- only the edge ID is required, not the records it connects. Routed through the same dispatcher as `POST /memory/manage/delete` with `type: 'edge'`.

Usage: Remove a single inferred or manually declared relationship between two records without touching either record's properties.

SDK: client.v1_1.memory.manage.deleteEdge(edgeId)

Parameters

FieldTypeWhereRequiredDescription
edgeIdstringpathYesEdge ID to delete.

Response

{
  "success": true,
  "receiptId": "del_edge_xyz",
  "type": "edge",
  "deletedCounts": { "edges": 1 },
  "items": [{ "id": "edge_abc123", "status": "deleted" }],
  "partialSuccess": false
}
  • Also reachable at `/memory/manage/:id/edges/:edgeId` -- the `:id` segment is accepted there for REST symmetry but not read; both paths call the exact same handler and only `edgeId` is ever used.
  • Irreversible hard delete -- no soft-delete or recovery window, unlike record delete.
DELETE/api/v1.1/memory/manage/:id/memories/:memoryIdSecret key

Delete individual memories

Deletes memory entries by ID, CRM key, or age. Same handler that backs v1's `POST /memory/delete`.

Usage: Remove one or more specific memory rows (not the whole record) -- for example, a stale or incorrect extracted fact.

Parameters

FieldTypeWhereRequiredDescription
idstringpathYesRecord ID segment, present for REST symmetry. Not read by the current handler -- see notes.
memoryIdstringpathYesMemory ID segment, present for REST symmetry. Not read by the current handler -- see notes.
idsstring[]bodyNoMemory IDs to delete. Pass the target memory id(s) here explicitly (including the one named in the URL) -- at least one of `ids`, `crmKeys`, or `olderThan` is required.
crmKeysobjectbodyNoAlternative to `ids`: `{ recordId, type }` (or `email` / `websiteUrl`) to delete every memory for a record.
olderThanstringbodyNoAlternative to `ids`: ISO date string -- deletes memories older than this.

Request

{ "ids": ["mem_abc123"] }

Response

{
  "success": true,
  "deletedCount": 1,
  "errors": []
}
  • The `:id` and `:memoryId` path segments are NOT bound to the request body by this route -- unlike the sibling `/manage/:id/*` routes, there is no bridging middleware here. Pass the memory id(s) to delete explicitly via `ids` in the JSON body, or the call fails with 400 ("At least one deletion criteria is required").
  • Deleting by plain memory IDs is an immediate, unrecoverable delete of the vector-store and freeform rows. Only IDs prefixed `REC#` (whole-record deletes passed through this same `ids` array) additionally soft-delete the DynamoDB RecordSnapshot -- individual memory-id deletes have no recovery window.

16 endpoints

Graph & Relation Types

Configure the property-to-edge inference graph used by memory saves: the relation type taxonomy (the vocabulary of edge kinds), per-org rules that map an entity property to an inferred edge, and org-level graph settings. Canonical at the v1.1 path `/api/v1.1/memory/manage/graph/*`. Relation types are one set of operations, not three separate features, despite appearing under both the graph and memory taxonomies. On the v1.1 mount, every WRITE route (create, batch create, update, delete) additionally requires `admin` scope on the API key, matching the rest of the `/memory/manage` taxonomy; reads are not admin-gated on either mount.

GET/api/v1.1/memory/manage/graph/configSecret keySDK

Get graph config bundle

Returns a full snapshot of the org's graph configuration: relation types, rules, and settings, in one call.

Usage: Use this to render a graph-configuration screen without three separate list calls.

SDK: client.v1_1.memory.graph.getConfig()

Response

{
  "success": true,
  "data": {
    "relationTypes": [
      { "typeName": "works_at", "displayLabel": "Works At", "category": "role", "isBuiltin": true, "isSingleValued": true, "isSymmetric": false, "inverseType": "employs", "defaultWeight": 1, "allowedFromTypes": ["contact"], "allowedToTypes": ["company"], "examples": [], "isActive": true, "scope": "system" }
    ],
    "rules": [
      { "ruleId": "rule_abc", "sourceEntityType": "contact", "propertyName": "company", "relationType": "works_at", "targetEntityType": "company", "valueShape": "scalar", "resolveBy": ["websiteUrl", "name"], "normalize": "companyHint", "allowStub": true, "allowSelfType": false, "inverse": false, "confidence": 0.9, "isBuiltin": true, "isActive": true, "scope": "system", "overridesSystem": false }
    ],
    "settings": { "inheritSystemRules": true }
  }
}
  • Empty/un-activated orgs (no kit installed yet) omit system built-ins from `relationTypes` and `rules`, so the response shows no phantom graph schema.
PUT/api/v1.1/memory/manage/graph/configSecret keySDK

Replace graph config bundle

Atomic bundle upsert: creates or updates relation types, then rules, then settings, in that order, from one payload.

Usage: Use this to provision a full graph schema (relation types + rules + settings) for an org in one call, for example from a setup kit.

SDK: client.v1_1.memory.graph.putConfig(body)

Parameters

FieldTypeWhereRequiredDescription
relationTypesobject[]bodyNoRelation types to create. Same shape as the create-relation-type body. Max 100.
rulesobject[]bodyNoRules to create. Same shape as the create-rule body. Max 200.
settingsobjectbodyNo`{ inheritSystemRules: boolean }`.

Response

{
  "success": true,
  "data": {
    "relationTypes": [ { "typeName": "works_at" } ],
    "rules": [ { "ruleId": "rule_abc" } ],
    "settings": { "inheritSystemRules": true }
  },
  "notice": "Graph rule saved. Synchronous edge writes and Fargate workers pick it up within ~5s (cache-invalidation log); the async inference Lambda within ~60s (cache TTL)."
}
  • All three sections are optional; omit a section to leave it unchanged.
  • Requires `admin` scope.
GET/api/v1.1/memory/manage/graph/rulesSecret keySDK

List graph rules

Lists the effective graph rules for the org: system built-ins plus per-org rules and overrides.

Usage: Use this to inspect which property-to-edge inference rules are active before writing a save that relies on Channel B (`collectionGraph`).

SDK: client.v1_1.memory.graph.listRules(opts)

Parameters

FieldTypeWhereRequiredDescription
includeInactivebooleanqueryNoInclude disabled/tombstoned rules.Default: false
sourceEntityTypestringqueryNoFilter to rules whose sourceEntityType matches.
includeSystembooleanqueryNoSet to `false` to exclude system built-in rules.Default: true

Response

{
  "success": true,
  "data": {
    "items": [
      { "ruleId": "rule_abc", "sourceEntityType": "contact", "propertyName": "company", "relationType": "works_at", "targetEntityType": "company", "valueShape": "scalar", "resolveBy": ["websiteUrl", "name"], "normalize": "companyHint", "allowStub": true, "allowSelfType": false, "inverse": false, "confidence": 0.9, "isBuiltin": true, "isActive": true, "scope": "system", "overridesSystem": false }
    ],
    "counts": { "system": 12, "org": 2, "tombstoned": 0 },
    "inheritSystemRules": true
  }
}
  • System built-in rules are hidden for an un-activated org (no kit installed yet), the same way `graph/config` hides them.
POST/api/v1.1/memory/manage/graph/rulesSecret keySDK

Create a graph rule

Creates a per-org rule mapping one source entity property to an inferred edge. `relationType` must already exist in the relation-types registry.

Usage: Use this to teach Channel B (`collectionGraph` on memory saves) to infer an edge whenever a given property is written.

SDK: client.v1_1.memory.graph.createRule(body)

Parameters

FieldTypeWhereRequiredDescription
sourceEntityTypestringbodyYesEntity type the property lives on. Must match `^[a-z][a-z0-9_]{0,40}$`.
propertyNamestringbodyYesProperty name that triggers the rule. 3-60 chars, alphanumeric/underscore/hyphen.
relationTypestringbodyYesExisting relation type name this rule infers. Must match `^[a-z][a-z0-9_]{2,40}$`.
targetEntityTypestringbodyYesEntity type the edge points to.
resolveBystring[]bodyYesIdentity kinds tried in order to resolve the edge target, strongest first.Options: email, websiteUrl, linkedinHandle, phoneNumber, salesforceId, crmDealId, name
valueShapestringbodyNoWhether the property holds one value or an array.Options: scalar, array
normalizestringbodyNoNormalizer applied to the property value before identity resolution.Options: email, domain, linkedinHandle, phone, lowercase, companyHint, identity
allowStubbooleanbodyNoAllow creating a stub target record when no match resolves. Not permitted when the strongest `resolveBy` kind is `name`.
allowSelfTypebooleanbodyNoRequired `true` when `sourceEntityType` equals `targetEntityType`, otherwise the rule is rejected as a no-op.
inversebooleanbodyNoWhether to also write the inverse edge.
confidencenumberbodyNoConfidence score for the inferred edge.Default: 0.9Range: 01
disabledbooleanbodyNoCreate the rule inactive. Useful to tombstone an inherited system rule of the same (sourceEntityType, propertyName).

Request

{
  "sourceEntityType": "contact",
  "propertyName": "referred_by_email",
  "relationType": "referred_by",
  "targetEntityType": "contact",
  "resolveBy": ["email"],
  "normalize": "email",
  "confidence": 0.85
}

Response

{
  "success": true,
  "data": { "ruleId": "rule_xyz", "sourceEntityType": "contact", "propertyName": "referred_by_email", "relationType": "referred_by", "targetEntityType": "contact", "valueShape": "scalar", "resolveBy": ["email"], "normalize": "email", "allowStub": false, "allowSelfType": true, "inverse": false, "confidence": 0.85, "isBuiltin": false, "isActive": true, "scope": "org", "overridesSystem": false },
  "notice": "Graph rule saved. Synchronous edge writes and Fargate workers pick it up within ~5s (cache-invalidation log); the async inference Lambda within ~60s (cache TTL)."
}
  • Returns 409 (`rule_conflict`) if a rule already exists for the same (sourceEntityType, propertyName) -- use PATCH to update it instead.
  • Requires `admin` scope.
POST/api/v1.1/memory/manage/graph/rules/batchSecret keySDK

Batch create graph rules

Creates up to 25 graph rules in one transaction. Same per-rule shape as the single-create endpoint.

Usage: Use this to provision several inference rules at once, for example when installing a setup kit.

SDK: client.v1_1.memory.graph.batchCreateRules(items)

Parameters

FieldTypeWhereRequiredDescription
itemsobject[]bodyYesArray of rule payloads, each shaped like the create-rule body. 1-25 items.

Response

{
  "success": true,
  "data": { "items": [ { "ruleId": "rule_1", "sourceEntityType": "contact", "propertyName": "company" } ], "count": 1 },
  "notice": "Graph rule saved. Synchronous edge writes and Fargate workers pick it up within ~5s (cache-invalidation log); the async inference Lambda within ~60s (cache TTL)."
}
  • Rejects the whole batch with 400 on a duplicate (sourceEntityType, propertyName) within the batch, and with 409 if any item already exists in the org.
  • Requires `admin` scope.
GET/api/v1.1/memory/manage/graph/rules/:ruleIdSecret keySDK

Get a graph rule

Fetches one graph rule by ID.

Usage: Use this to inspect a specific rule before patching or deleting it.

SDK: client.v1_1.memory.graph.getRule(ruleId)

Parameters

FieldTypeWhereRequiredDescription
ruleIdstringpathYesRule ID.

Response

{
  "success": true,
  "data": { "ruleId": "rule_abc", "sourceEntityType": "contact", "propertyName": "company", "relationType": "works_at", "targetEntityType": "company", "isActive": true, "scope": "org" }
}
  • Returns 404 (`not_found`) if the rule does not exist in this org.
PATCH/api/v1.1/memory/manage/graph/rules/:ruleIdSecret keySDK

Update a graph rule

Partial update of a graph rule. `sourceEntityType` and `propertyName` are immutable.

Usage: Use this to change what a rule infers, or to flip `isActive` to disable it.

SDK: client.v1_1.memory.graph.updateRule(ruleId, body)

Parameters

FieldTypeWhereRequiredDescription
ruleIdstringpathYesRule ID.
relationTypestringbodyNoNew relation type this rule infers.
targetEntityTypestringbodyNoNew target entity type.
valueShapestringbodyNoUpdate the value shape.Options: scalar, array
resolveBystring[]bodyNoReplace the identity-kind resolution order.
normalizestringbodyNoUpdate the normalizer.
allowStubbooleanbodyNoUpdate the stub-creation flag.
allowSelfTypebooleanbodyNoUpdate the self-type flag.
inversebooleanbodyNoUpdate whether the inverse edge is also written.
confidencenumberbodyNoUpdate the confidence score.Range: 01
isActivebooleanbodyNoEnable or disable the rule.

Response

{
  "success": true,
  "data": { "ruleId": "rule_abc", "isActive": false },
  "notice": "Graph rule saved. Synchronous edge writes and Fargate workers pick it up within ~5s (cache-invalidation log); the async inference Lambda within ~60s (cache TTL)."
}
  • Returns 403 (`system_rule_immutable`) if `ruleId` is a built-in system rule -- create an org rule with the same (sourceEntityType, propertyName) to override it instead.
  • Requires `admin` scope.
DELETE/api/v1.1/memory/manage/graph/rules/:ruleIdSecret keySDK

Delete a graph rule

Soft-deletes a per-org graph rule by setting it inactive.

Usage: Use this to remove an inference rule you no longer want firing.

SDK: client.v1_1.memory.graph.deleteRule(ruleId)

Parameters

FieldTypeWhereRequiredDescription
ruleIdstringpathYesRule ID.

Response

{
  "success": true,
  "data": { "ruleId": "rule_abc", "sourceEntityType": "contact", "propertyName": "company", "isActive": false, "scope": "org" },
  "notice": "Graph rule saved. Synchronous edge writes and Fargate workers pick it up within ~5s (cache-invalidation log); the async inference Lambda within ~60s (cache TTL)."
}
  • Returns 403 (`system_rule_immutable`) for built-in system rules -- they cannot be deleted, only overridden with a disabled org rule of the same key.
  • Requires `admin` scope.
GET/api/v1.1/memory/manage/graph/settingsSecret keySDK

Get graph settings

Returns the org's `inheritSystemRules` flag.

Usage: Use this to check whether the org currently inherits the platform's built-in graph rules.

SDK: client.v1_1.memory.graph.getSettings()

Response

{
  "success": true,
  "data": { "inheritSystemRules": true }
}
PATCH/api/v1.1/memory/manage/graph/settingsSecret keySDK

Update graph settings

Sets the org's `inheritSystemRules` flag.

Usage: Use this to opt an org out of the platform's built-in graph rules and rely entirely on org-defined rules.

SDK: client.v1_1.memory.graph.updateSettings(body)

Parameters

FieldTypeWhereRequiredDescription
inheritSystemRulesbooleanbodyYesWhether system built-in rules apply on top of org rules.

Request

{ "inheritSystemRules": false }

Response

{
  "success": true,
  "data": { "inheritSystemRules": false },
  "notice": "Graph rule saved. Synchronous edge writes and Fargate workers pick it up within ~5s (cache-invalidation log); the async inference Lambda within ~60s (cache TTL)."
}
  • Requires `admin` scope.
GET/api/v1.1/memory/manage/relation-typesSecret keySDK

List relation types

Lists the relation type taxonomy for the org: system built-ins plus per-org types.

Usage: Use this to see which edge kinds (`works_at`, `referred_by`, etc.) are available before referencing one in a graph rule or a `relations[]` declared edge on a memory save.

SDK: client.v1_1.memory.listRelationTypes(opts)

Parameters

FieldTypeWhereRequiredDescription
includeInactivebooleanqueryNoInclude disabled types.Default: false
includeSystembooleanqueryNoSet to `false` to exclude system built-in types.Default: true

Response

{
  "success": true,
  "data": {
    "items": [
      { "typeName": "works_at", "displayLabel": "Works At", "description": null, "category": "role", "isBuiltin": true, "isSingleValued": true, "isSymmetric": false, "inverseType": "employs", "defaultWeight": 1, "allowedFromTypes": ["contact"], "allowedToTypes": ["company"], "examples": [], "isActive": true, "scope": "system", "createdAt": "2026-01-01T00:00:00.000Z", "updatedAt": "2026-01-01T00:00:00.000Z" }
    ],
    "counts": { "system": 18, "org": 3 }
  }
}
  • System built-in types are hidden for an un-activated org (no kit installed yet).
POST/api/v1.1/memory/manage/relation-typesSecret keySDK

Create a relation type

Creates a per-org relation type.

Usage: Use this to define a new edge kind before referencing it in graph rules or declared `relations[]` on a memory save.

SDK: client.v1_1.memory.createRelationType(body)

Parameters

FieldTypeWhereRequiredDescription
typeNamestringbodyYesUnique relation type name. Must match `^[a-z][a-z0-9_]{2,40}$`.
displayLabelstringbodyNoHuman-readable label (1-120 chars).
descriptionstringbodyNoDescription (max 2000 chars).
categorystringbodyNoClassification.Options: biographical, role, activity, mention, generic
isSingleValuedbooleanbodyNoWhether a source record can only hold one edge of this type.
isSymmetricbooleanbodyNoWhether the edge reads the same in both directions.
inverseTypestringbodyNoRelation type name to use for the inverse edge.
defaultWeightnumberbodyNoDefault edge weight.Default: 1.0Range: 01
allowedFromTypesstring[]bodyNoEntity types allowed as the edge source. Omit to allow any.
allowedToTypesstring[]bodyNoEntity types allowed as the edge target. Omit to allow any.
examplesstring[]bodyNoExample sentences illustrating the relation. Max 10 entries, 240 chars each.

Request

{
  "typeName": "referred_by",
  "displayLabel": "Referred By",
  "category": "role",
  "isSingleValued": true,
  "allowedFromTypes": ["contact"],
  "allowedToTypes": ["contact"]
}

Response

{
  "success": true,
  "data": { "typeName": "referred_by", "displayLabel": "Referred By", "description": null, "category": "role", "isBuiltin": false, "isSingleValued": true, "isSymmetric": false, "inverseType": null, "defaultWeight": 1, "allowedFromTypes": ["contact"], "allowedToTypes": ["contact"], "examples": [], "isActive": true, "scope": "org", "createdAt": "2026-09-01T00:00:00.000Z", "updatedAt": "2026-09-01T00:00:00.000Z" },
  "notice": "Graph rule saved. Synchronous edge writes and Fargate workers pick it up within ~5s (cache-invalidation log); the async inference Lambda within ~60s (cache TTL)."
}
  • Returns 400 (`validation_failed`) if `inverseType` references a type that does not exist or is inactive in this org.
  • Requires `admin` scope.
POST/api/v1.1/memory/manage/relation-types/batchSecret keySDK

Batch create relation types

Creates up to 25 relation types in one transaction.

Usage: Use this to provision a full relation-type vocabulary at once, for example when installing a setup kit.

SDK: client.v1_1.memory.batchCreateRelationTypes(items)

Parameters

FieldTypeWhereRequiredDescription
itemsobject[]bodyYesArray of relation type payloads, each shaped like the create body. 1-25 items.

Response

{
  "success": true,
  "data": { "items": [ { "typeName": "referred_by", "scope": "org" } ], "count": 1 },
  "notice": "Graph rule saved. Synchronous edge writes and Fargate workers pick it up within ~5s (cache-invalidation log); the async inference Lambda within ~60s (cache TTL)."
}
  • Requires `admin` scope.
GET/api/v1.1/memory/manage/relation-types/:typeNameSecret keySDK

Get a relation type

Fetches one relation type by name.

Usage: Use this to inspect a relation type's allowed from/to types before referencing it in a graph rule.

SDK: client.v1_1.memory.getRelationType(typeName)

Parameters

FieldTypeWhereRequiredDescription
typeNamestringpathYesRelation type name.

Response

{
  "success": true,
  "data": { "typeName": "works_at", "displayLabel": "Works At", "category": "role", "isBuiltin": true, "scope": "system" }
}
  • Returns 404 (`not_found`) if the type does not exist in this org.
PATCH/api/v1.1/memory/manage/relation-types/:typeNameSecret keySDK

Update a relation type

Partial update of a per-org relation type. `typeName` is immutable. `null` on a nullable field clears it.

Usage: Use this to adjust a relation type's metadata, symmetry, weight, or allowed entity types.

SDK: client.v1_1.memory.updateRelationType(typeName, body)

Parameters

FieldTypeWhereRequiredDescription
typeNamestringpathYesRelation type name.
displayLabelstringbodyNoNew label. `null` clears it.
descriptionstringbodyNoNew description. `null` clears it.
categorystringbodyNoNew classification.Options: biographical, role, activity, mention, generic
isSingleValuedbooleanbodyNoUpdate single-valued flag.
isSymmetricbooleanbodyNoUpdate symmetric flag.
inverseTypestringbodyNoNew inverse type name. `null` clears it.
defaultWeightnumberbodyNoNew default weight.Range: 01
allowedFromTypesstring[]bodyNoNew allowed source entity types. `null` clears the restriction.
allowedToTypesstring[]bodyNoNew allowed target entity types. `null` clears the restriction.
examplesstring[]bodyNoNew example sentences. `null` clears them.

Response

{
  "success": true,
  "data": { "typeName": "referred_by", "isSymmetric": true },
  "notice": "Graph rule saved. Synchronous edge writes and Fargate workers pick it up within ~5s (cache-invalidation log); the async inference Lambda within ~60s (cache TTL)."
}
  • Requires `admin` scope.
DELETE/api/v1.1/memory/manage/relation-types/:typeNameSecret keySDK

Delete a relation type

Soft-deletes a per-org relation type by setting it inactive.

Usage: Use this to remove a relation type you no longer use.

SDK: client.v1_1.memory.deleteRelationType(typeName)

Parameters

FieldTypeWhereRequiredDescription
typeNamestringpathYesRelation type name.

Response

{
  "success": true,
  "data": { "typeName": "referred_by", "isActive": false, "scope": "org" },
  "notice": "Graph rule saved. Synchronous edge writes and Fargate workers pick it up within ~5s (cache-invalidation log); the async inference Lambda within ~60s (cache TTL)."
}
  • Built-in system types cannot be deleted.
  • Requires `admin` scope.

22 endpoints

Governance And Schema

Manage guidelines, entity types, and schema collections that shape retrieval, extraction, and agent behavior.

GET/api/v1.1/guidelinesSecret keySDK

List guidelines

Lists guidelines with pagination and tag-based filters.

Usage: Use this to build governance pickers, admin tables, or sync flows.

SDK: client.guidelines.list()

Parameters

FieldTypeWhereRequiredDescription
limitnumberqueryNoPage size.Default: 20
nextTokenstringqueryNoCursor from a previous page.
tagsstring | string[]queryNoOnly include these tags.
excludeTagsstring | string[]queryNoExclude these tags.
summarybooleanqueryNoOmit the full `value` payload.

Response

{
  "success": true,
  "data": {
    "actions": [
      {
        "id": "act_123",
        "type": "variables",
        "payload": {
          "name": "ICP",
          "description": "Ideal customer profile",
          "tags": ["sales"]
        }
      }
    ],
    "count": 1,
    "nextToken": "eyJ..."
  }
}

SDK Example

const result = await client.guidelines.list({
  tags: ["sales"],
  limit: 10,
});
console.log(result.data.actions);
POST/api/v1.1/guidelinesSecret keySDK

Create a guideline

Creates a new guideline or governance variable.

Usage: Use this to seed policy, tone, or playbook content programmatically.

SDK: client.guidelines.create()

Parameters

FieldTypeWhereRequiredDescription
namestringbodyYesGuideline name.
valuestringbodyNoFull markdown or text content.
descriptionstringbodyNoShort summary.
tagsstring[]bodyNoTags for filtering and routing.
securebooleanbodyNoMarks the value as sensitive.
slugstringbodyNoSDK compatibility alias.

Request

{
  "name": "Brand Voice",
  "description": "How our outbound voice should sound",
  "value": "# Tone\nConfident, direct, and specific.",
  "tags": ["marketing"]
}

Response

{
  "success": true,
  "data": {
    "id": "act_123",
    "type": "variables"
  }
}

SDK Example

const result = await client.guidelines.create({
  name: "Brand Voice",
  description: "How our outbound voice should sound",
  value: "# Tone\nConfident, direct, and specific.",
  tags: ["marketing"],
});
console.log(result.data.id);
GET/api/v1.1/guidelines/:id/structureSecret keySDK

Guideline heading structure

Returns the extracted markdown headings for one guideline.

Usage: Use this when you want section-aware editing or selective retrieval.

SDK: client.guidelines.getStructure()

Parameters

FieldTypeWhereRequiredDescription
idstringpathYesGuideline ID.

Response

{
  "success": true,
  "data": {
    "headings": ["Tone", "Do", "Do Not"]
  }
}

SDK Example

const result = await client.guidelines.getStructure("act_123");
console.log(result.data.headings);
GET/api/v1.1/guidelines/:id/sectionSecret keySDK

Guideline section content

Returns the content under a specific heading inside a guideline.

Usage: Use this for focused reads instead of fetching the full guideline value.

SDK: client.guidelines.getSection()

Parameters

FieldTypeWhereRequiredDescription
idstringpathYesGuideline ID.
headerstringqueryYesHeading to extract.

Response

{
  "success": true,
  "data": {
    "header": "Tone",
    "content": "Confident, direct, and specific."
  }
}

SDK Example

const result = await client.guidelines.getSection("act_123", {
  header: "Tone",
});
console.log(result.data.content);
PATCH/api/v1.1/guidelines/:idSecret keySDK

Update a guideline

Partially updates guideline metadata or content.

Usage: Use this to replace content, append sections, or patch one heading in place.

SDK: client.guidelines.update()

Parameters

FieldTypeWhereRequiredDescription
namestringbodyNoRename the guideline.
valuestringbodyNoReplacement or appended content.
descriptionstringbodyNoUpdated summary.
tagsstring[]bodyNoUpdated tags.
securebooleanbodyNoUpdated security flag.
updateModestringbodyNoPatch mode.Default: replaceOptions: replace, append, section, appendToSection
sectionHeaderstringbodyNoTarget heading for section updates.
separatorstringbodyNoSeparator inserted during append operations.
historyNotestringbodyNoHuman-readable change note.

Request

{
  "updateMode": "section",
  "sectionHeader": "Tone",
  "value": "Confident, direct, and helpful.",
  "historyNote": "Refined messaging voice"
}

Response

{
  "success": true,
  "data": {
    "id": "act_123",
    "updated": true
  }
}

SDK Example

const result = await client.guidelines.update("act_123", {
  updateMode: "section",
  sectionHeader: "Tone",
  value: "Confident, direct, and helpful.",
});
console.log(result.data.updated);
DELETE/api/v1.1/guidelines/:idSecret keySDK

Delete a guideline

Deletes a guideline by ID.

Usage: Use this to remove governance content that should no longer route into Smart Guidelines or agents.

SDK: client.guidelines.delete()

Parameters

FieldTypeWhereRequiredDescription
idstringpathYesGuideline ID.

Response

{
  "success": true,
  "message": "Guideline deleted"
}

SDK Example

await client.guidelines.delete("act_123");
GET/api/v1.1/guidelines/downloadSecret key

Download guidelines as a zip

Builds (or serves a cached) zip of the org's guideline-type context docs, including their attachments, and returns a presigned S3 download URL. Same underlying handler as `GET /context/manage/download`, but this path pre-filters to `type: 'guideline'` documents only.

Usage: Export governance content -- guidelines plus their attachments -- as a portable zip archive, for example for backup or handoff to another system.

Parameters

FieldTypeWhereRequiredDescription
scopestringqueryNoSet to `platform` to download the pre-built, publicly-hosted platform guideline pack instead of the org's own guidelines.Options: platform

Response

{
  "success": true,
  "url": "https://...presigned-s3-url...",
  "scope": "org",
  "type": "guideline",
  "count": 12,
  "cached": false
}
  • `?scope=platform` requires NO authentication and skips the type filter entirely -- it returns a pre-built platform-wide zip (`note: "Pre-built at last seed. Re-run seed script to update."`), not the caller's org guidelines.
  • Without `?scope=platform`, results are cached per org+type for a TTL; `cached: true` on a cache hit.
  • The zip includes each doc's content, a `meta.json`, and its attachments, plus a top-level `manifest.json`.
  • Unlike `GET /context/manage/download`, this route always injects `type: 'guideline'` server-side -- there is no way to widen it back to all doc types on this path.
POST/api/v1.1/smart-updateSecret keySDK

Smart Update

AI-powered governance and schema evolution. Analyzes instruction + raw material against existing guidelines or collections and returns (or applies) a structured change plan.

Usage: Use when you have unstructured material (policy docs, meeting notes, feedback, competitor analysis, CRM exports) that should become structured governance or schema. The AI figures out what to create, update, or flag as conflicting.

SDK: client.guidelines.smartUpdate()

Parameters

FieldTypeWhereRequiredDescription
typestringbodyYesWhat to update.Options: guideline, collection
instructionstringbodyYesWhat to do with the material. Be specific about the goal.
materialstringbodyYesRaw content: text, notes, JSON, meeting notes, policy docs, etc. Max 50K chars.
strategystringbodyNoExecution strategy.Default: suggestOptions: suggest, safe, force

Request

{
  "type": "guideline",
  "instruction": "Update our sales guidelines with this new enterprise discount tier",
  "material": "Enterprise customers (500+ seats) can now get 15% discount with VP approval. Standard pricing remains $49/seat.",
  "strategy": "suggest"
}

Response

{
  "success": true,
  "data": {
    "status": "planned",
    "creditsUsed": 3,
    "items": [
      {
        "itemId": "item_1",
        "action": "update_section",
        "target": "sales-playbook",
        "targetId": "act_123",
        "sectionHeader": "## Pricing",
        "reasoning": "Material contains new enterprise discount tier",
        "detail": "Updates pricing section with enterprise tier",
        "preview": {
          "before": "Standard: $49/seat/month.",
          "after": "Standard: $49/seat/month.\nEnterprise (500+ seats): 15% volume discount. Requires VP approval."
        },
        "hasConflict": false,
        "applied": false
      }
    ],
    "summary": "Added enterprise discount tier to sales playbook pricing section."
  }
}

SDK Example

// Suggest mode (plan only, no writes)
const plan = await client.guidelines.smartUpdate({
  type: "guideline",
  instruction: "Update sales guidelines with new enterprise pricing",
  material: "Enterprise customers (500+ seats) can now get 15% discount...",
  strategy: "suggest",
});

// Review the plan
for (const item of plan.data.items) {
  console.log(`${item.action} on ${item.target}: ${item.detail}`);
}

// Apply non-conflicting changes
const applied = await client.guidelines.smartUpdate({
  type: "guideline",
  instruction: "Update sales guidelines with new enterprise pricing",
  material: "Enterprise customers (500+ seats) can now get 15% discount...",
  strategy: "safe",
});
  • 3 credits per call
  • Material trimmed at 50K characters with warning
  • Use strategy='suggest' first to review plan before applying
GET/api/v1.1/guidelines/{id}/attachmentsSecret keySDK

List Guideline Attachments

List all file attachments for a specific guideline. Returns metadata for each attachment including name, type, description, usage, audit status, and fetch analytics.

Usage: Discover what resources (scripts, templates, configs, data files, prompts, images) are attached to a guideline before accessing their content.

SDK: client.guidelines.listAttachments(guidelineId)

Parameters

FieldTypeWhereRequiredDescription
idstringpathYesGuideline ID

Request

GET /api/v1.1/guidelines/guide-abc123/attachments

Response

{ "success": true, "data": [{ "id": "att_a1b2c3", "name": "compliance-check.py", "type": "script", "description": "GDPR compliance validator", "usage": "Run before sending emails with PII", "language": "python", "sizeBytes": 2340, "audit": { "status": "clean", "findings": [] } }] }

SDK Example

const attachments = await client.guidelines.listAttachments("guide-abc123");
  • 0 credits
  • Returns metadata only -- use Get Attachment Content for file data
POST/api/v1.1/guidelines/{id}/attachmentsSecret key

Upload Guideline Attachment

Upload a file attachment to a guideline. Supports scripts, templates, references, configs, data files, schemas, prompts, and images. Files are automatically audited for quality and security by an LLM.

Usage: Attach actionable resources to guidelines so AI agents can access scripts, templates, and reference files alongside governance policies.

SDK: client.guidelines.uploadAttachment(guidelineId, options)

Parameters

FieldTypeWhereRequiredDescription
idstringpathYesGuideline ID
filefilebodyYesFile to upload (multipart/form-data)
metadata.typestringbodyYesAttachment type: script, template, reference, config, data, schema, prompt, image
metadata.descriptionstringbodyYesWhat this file is (used for SmartContext routing)
metadata.usagestringbodyYesWhen and how agents should use this file
metadata.languagestringbodyNoProgramming language for scripts: python, javascript, typescript, bash, sql, etc.

Request

POST /api/v1.1/guidelines/guide-abc123/attachments
Content-Type: multipart/form-data

file: <binary>
metadata: { "type": "script", "description": "GDPR compliance validator", "usage": "Run before sending emails with PII", "language": "python" }

Response

{ "success": true, "data": { "id": "att_a1b2c3", "name": "compliance-check.py", "type": "script", "sizeBytes": 2340, "audit": { "status": "pending" } } }

SDK Example

const att = await client.guidelines.uploadAttachment("guide-abc123", {
  file: fs.readFileSync("./compliance-check.py"),
  type: "script",
  description: "GDPR compliance validator",
  usage: "Run before sending emails with PII",
  language: "python",
});
  • 0 credits
  • Max 5 MB per file
  • Max 10 attachments per guideline
  • LLM audit runs automatically after upload
GET/api/v1.1/guidelines/{id}/attachments/{attachmentId}/contentSecret keySDK

Get Attachment Content

Download the full content of an attachment. Returns the raw file with appropriate Content-Type header.

Usage: Fetch actual file content for execution, rendering, or analysis. Each download is tracked for usage analytics.

SDK: client.guidelines.getAttachmentContent(guidelineId, attachmentId)

Parameters

FieldTypeWhereRequiredDescription
idstringpathYesGuideline ID
attachmentIdstringpathYesAttachment ID

Request

GET /api/v1.1/guidelines/guide-abc123/attachments/att_a1b2c3/content

Response

Raw file content with Content-Type header matching the file MIME type

SDK Example

const content = await client.guidelines.getAttachmentContent("guide-abc123", "att_a1b2c3");
  • 0 credits
  • Increments fetch count on the attachment for analytics
DELETE/api/v1.1/guidelines/{id}/attachments/{attachmentId}Secret keySDK

Delete Attachment

Permanently delete an attachment from a guideline. Removes both the file from storage and the metadata record.

Usage: Remove outdated, incorrect, or unnecessary attachments from guidelines.

SDK: client.guidelines.deleteAttachment(guidelineId, attachmentId)

Parameters

FieldTypeWhereRequiredDescription
idstringpathYesGuideline ID
attachmentIdstringpathYesAttachment ID

Request

DELETE /api/v1.1/guidelines/guide-abc123/attachments/att_a1b2c3

Response

{ "success": true }

SDK Example

await client.guidelines.deleteAttachment("guide-abc123", "att_a1b2c3");
  • 0 credits
  • Irreversible
POST/api/v1.1/guidelines/{id}/attachments/batchSecret keySDK

Batch Upload Guideline Attachments

Uploads multiple file attachments to a guideline in one multipart request, each with its own metadata. Same validation, storage, and LLM audit pipeline as the single-file upload.

Usage: Attach several resources (scripts, templates, configs) to a guideline in one call instead of one request per file.

SDK: client.guidelines.batchUploadAttachments(guidelineId, options)

Parameters

FieldTypeWhereRequiredDescription
idstringpathYesGuideline ID
filesfile[]bodyYesUp to 10 files (multipart/form-data field name `files`).
manifestobject[]bodyYesJSON array of per-file metadata, same index order as `files`. Each entry: `{ type, description, usage, name?, language?, sectionHeader? }` (same shape as single-upload `metadata`).

Request

POST /api/v1.1/guidelines/guide-abc123/attachments/batch
Content-Type: multipart/form-data

files: <binary>, <binary>
manifest: [{ "type": "script", "description": "...", "usage": "..." }, { "type": "reference", "description": "...", "usage": "..." }]

Response

{ "success": true, "data": { "uploaded": [{ "id": "att_a1b2c3", "name": "..." }, { "id": "att_d4e5f6", "name": "..." }], "warnings": [] } }

SDK Example

const result = await client.guidelines.batchUploadAttachments("guide-abc123", {
  files: [{ content: fs.readFileSync("./a.py"), metadata: { type: "script", description: "...", usage: "..." } }],
});
  • Returns 201 on success.
  • Max 10 files per batch call. A single invalid manifest entry (e.g. bad `type`) rejects the entire batch (400, `batch rejected`) -- no partial upload.
  • The identical operation exists scoped to a context document instead of a guideline -- see `POST /context/manage/{id}/attachments/batch` in the Context category. Both mount the same underlying route factory.
GET/api/v1.1/attachments/statsSecret keySDK

Org-wide attachment stats

Returns total attachment count, total storage bytes, and a per-type breakdown across every attachment in the org -- guideline-scoped and context-doc-scoped combined.

Usage: Monitor storage usage against the org's attachment storage quota before it's exceeded.

SDK: client.attachments.stats()

Response

{
  "success": true,
  "data": {
    "totalCount": 42,
    "totalSizeBytes": 1048576,
    "countByType": { "script": 10, "reference": 32 }
  }
}

SDK Example

const stats = await client.attachments.stats();
console.log(stats.data.totalSizeBytes);
  • Counts every attachment in the org regardless of which guideline or context document it's scoped to.
  • The same-looking path under a scoped mount (for example `GET /guidelines/{id}/attachments/stats`, `GET /context/{id}/attachments/stats`, `GET /context/manage/{id}/attachments/stats`) is NOT a separate stats endpoint -- those routers have no `/stats` route, so it falls through to "get attachment by id" with id=`stats` and returns 404 (`not_found`) unless an attachment happens to be literally named that. Use this org-level endpoint for stats instead.
GET/api/v1.1/attachmentsSecret key

List all org attachments

Lists attachments across the whole org (both guideline- and context-doc-scoped), with optional type/search filtering and pagination.

Usage: Browse or search every attachment in the org without knowing in advance which guideline or context doc it's attached to.

Parameters

FieldTypeWhereRequiredDescription
typestringqueryNoFilter by attachment type.Options: script, template, reference, config, data, schema, prompt, image
searchstringqueryNoCase-insensitive match against attachment name or description.
limitnumberqueryNoPage size.
nextTokenstringqueryNoPagination cursor from a previous page.

Response

{
  "success": true,
  "data": [
    { "id": "att_a1b2c3", "name": "compliance-check.py", "type": "script", "sizeBytes": 2340 }
  ],
  "nextToken": null
}
  • No SDK wrapper yet -- call this path directly.
  • There is no org-level batch-upload or single-upload route; uploads must go through a scoped mount (`POST /guidelines/{id}/attachments`, `POST /context/manage/{id}/attachments`, or their `/batch` equivalents).
GET/api/v1.1/entitiesSecret key

List entity types

Lists entity types available to the current organization.

Usage: Use this to inspect or seed the entity model that collections and memory records rely on.

Parameters

FieldTypeWhereRequiredDescription
limitnumberqueryNoPage size.Default: 20
nextTokenstringqueryNoCursor from a previous page.

Response

{
  "success": true,
  "data": {
    "entities": [
      { "id": "ent_contact", "name": "Contact", "status": "Active" }
    ],
    "count": 1
  }
}
  • These routes are server-only today and are not wrapped by the SDK.
POST/api/v1.1/entitiesSecret key

Create an entity type

Creates a new entity type for your organization.

Usage: Use this when your product needs memory records outside the default Contact and Company models.

Parameters

FieldTypeWhereRequiredDescription
namestringbodyYesEntity display name.
slugstringbodyNoURL-safe identifier.
propertiesobject[]bodyNoInitial property definitions.
identifierColumnstringbodyNoPrimary identifier field name.

Request

{
  "name": "Partner",
  "identifierColumn": "Partner ID"
}

Response

{
  "success": true,
  "data": {
    "id": "ent_partner",
    "name": "Partner"
  }
}
GET/api/v1.1/collectionsSecret keySDK

List collections

Lists property collections with pagination and tag filters.

Usage: Use this to discover schemas available for extraction or record management.

SDK: client.collections.list()

Parameters

FieldTypeWhereRequiredDescription
limitnumberqueryNoPage size.Default: 20
nextTokenstringqueryNoCursor from a previous page.
tagsstring | string[]queryNoOnly include these tags.
excludeTagsstring | string[]queryNoExclude these tags.
summarybooleanqueryNoReturn lighter-weight collection payloads.

Response

{
  "success": true,
  "data": {
    "collections": [
      { "id": "col_contact", "collectionName": "Contact Properties" }
    ],
    "count": 1
  }
}

SDK Example

const result = await client.collections.list({
  tags: ["sales"],
  limit: 10,
});
console.log(result.data.collections);
POST/api/v1.1/collectionsSecret keySDK

Create a collection

Creates a collection and optionally seeds its property schema. Each property defines a field that the AI extraction pipeline will populate when content is memorized.

Usage: Use this to define extraction-ready schemas for contacts, companies, or custom entity types.

SDK: client.collections.create()

Parameters

FieldTypeWhereRequiredDescription
collectionNamestringbodyYesDisplay name (e.g., 'Contact Properties', 'Deal Tracker').
entityTypestringbodyNoEntity type: 'Contact', 'Company', 'Deal', 'Employee', or any custom type.
definitionstringbodyNoDescription or extraction instructions for the whole collection. Helps AI understand the collection's purpose.
propertiesPropertyDefinition[]bodyNoInitial property definitions. Can be added later via PATCH. Each property accepts: `propertyName` (required), `systemName` (auto-generated if omitted, immutable), `type` ('text' default, 'number', 'boolean', 'array', 'date', 'options'), `description` (extraction instructions for AI), `update` (true=replaceable, false=append-only), `options` (example values or comma-separated list), `tags` (string[] for extraction boosting), `status` ('Active' or 'Deleted').
namestringbodyNoSDK alias for `collectionName`.
collectionIdstringbodyNoOptional custom ID. Auto-generated (UUID) if omitted.

Request

{
  "collectionName": "Contact Properties",
  "entityType": "Contact",
  "definition": "Customer profile fields for GTM workflows",
  "properties": [
    {
      "propertyName": "Lifecycle Stage",
      "type": "options",
      "options": "Lead, MQL, SQL, Customer, Churned",
      "description": "Current funnel stage",
      "update": true
    },
    {
      "propertyName": "Meeting Notes",
      "type": "array",
      "description": "Chronological log of meeting summaries and action items",
      "update": false,
      "tags": ["interaction"]
    }
  ]
}

Response

{
  "success": true,
  "data": {
    "id": "col_contact",
    "collectionName": "Contact Properties"
  }
}

SDK Example

const result = await client.collections.create({
  collectionName: "Contact Properties",
  entityType: "Contact",
  properties: [
    { propertyName: "Lifecycle Stage", type: "options", options: "Lead, MQL, SQL, Customer", update: true },
    { propertyName: "Meeting Notes", type: "array", description: "Chronological meeting log", update: false, tags: ["interaction"] },
  ],
});
console.log(result.data.id);
PATCH/api/v1.1/collections/:idSecret keySDK

Update a collection

Updates collection metadata or adds/modifies property definitions. **Incremental**: send only the properties you want to add or change -- existing properties not included in the request are preserved unchanged.

Usage: Use this to add new properties to an existing collection, modify property definitions, rename the collection, or soft-delete properties. You do NOT need to send the full property list.

SDK: client.collections.update()

Parameters

FieldTypeWhereRequiredDescription
idstringpathYesCollection ID.
collectionNamestringbodyNoUpdated display name (only if renaming).
definitionstringbodyNoUpdated description or extraction instructions.
propertiesPropertyDefinition[]bodyNoProperties to add or modify. Send only new or changed properties -- existing ones are preserved. Each property accepts: `propertyName` (required), `systemName`, `type`, `description`, `update`, `options`, `tags`, `status`. See Create endpoint for full field reference.
historyNotestringbodyNoHuman-readable change note for the audit trail.

Request

{
  "properties": [
    {
      "propertyName": "Location",
      "type": "text",
      "description": "City or region where the contact is based (e.g., 'San Francisco', 'London')",
      "update": true,
      "tags": ["identity"]
    },
    {
      "propertyName": "Budget Range",
      "type": "options",
      "options": "<50K, 50K-200K, 200K-1M, >1M",
      "description": "Estimated annual budget for our product category",
      "update": true,
      "tags": ["qualification"]
    }
  ],
  "historyNote": "Added Location and Budget Range properties for lead scoring"
}

Response

{
  "success": true,
  "data": {
    "id": "col_contact",
    "updated": true
  }
}

SDK Example

// Add a single property to an existing collection
const result = await client.collections.update("col_contact", {
  properties: [
    { propertyName: "Location", type: "text", description: "City or region", update: true, tags: ["identity"] },
  ],
  historyNote: "Added Location property",
});

// Add multiple properties at once
await client.collections.update("col_contact", {
  properties: [
    { propertyName: "Budget Range", type: "options", options: "<50K, 50K-200K, 200K-1M, >1M", update: true },
    { propertyName: "Decision Timeline", type: "text", description: "When they plan to buy", update: true },
    { propertyName: "Objections Log", type: "array", description: "Sales objections raised", update: false },
  ],
  historyNote: "Added qualification properties for lead scoring",
});

// Soft-delete a property
await client.collections.update("col_contact", {
  properties: [
    { propertyName: "Old Field", systemName: "old_field", status: "Deleted" },
  ],
  historyNote: "Retired Old Field property",
});
  • **Incremental updates**: Only send properties you want to add or change. Existing properties not in the request are preserved.
  • **Adding properties**: Include the new property in the `properties` array. Future memorize calls will automatically extract values for it.
  • **Soft-deleting properties**: Set `status: 'Deleted'` on a property to remove it from extraction without deleting stored values.
  • **Modifying properties**: Send the property with its existing `systemName` and the fields you want to change. `systemName` is immutable and identifies which property to update.
  • **Retroactive extraction**: Existing records are NOT retroactively updated when you add a property. Re-memorize content to populate new properties on existing records.
DELETE/api/v1.1/collections/:idSecret keySDK

Delete a collection

Deletes a collection by ID.

Usage: Use this to remove obsolete schemas.

SDK: client.collections.delete()

Parameters

FieldTypeWhereRequiredDescription
idstringpathYesCollection ID.

Response

{
  "success": true,
  "message": "Collection deleted"
}

SDK Example

await client.collections.delete("col_contact");
  • System collections cannot be deleted.
GET/api/v1.1/collections/:id/historySecret keySDK

Collection history

Returns historical versions for a collection, including optional diff mode.

Usage: Use this to audit schema changes or power version history views.

SDK: client.collections.history()

Parameters

FieldTypeWhereRequiredDescription
idstringpathYesCollection ID.
modestringqueryNoHistory mode.Default: fullOptions: full, diff
limitnumberqueryNoPage size.Default: 20

Response

{
  "success": true,
  "data": {
    "actionId": "act_col123",
    "mode": "diff",
    "versions": [
      {
        "timestamp": "2026-03-18T12:00:00.000Z",
        "historyNote": "Added budget property",
        "changes": {
          "propertiesAdded": [],
          "propertiesRemoved": [],
          "propertiesModified": []
        }
      }
    ]
  }
}

SDK Example

const result = await client.collections.history("col_contact", {
  mode: "diff",
});
console.log(result.data.versions);

22 endpoints

Context

Knowledge layer for agent-readable documents. v1.1 introduces (1) the `shape='document'` path on `/memory/save` for single-doc writes, (2) a PG-backed doc-type registry under `/context/manage/doc-types/*`, and (3) async bulk-save under `/context/save/batch/*` (1-24h SLA via provider batch APIs). The legacy `agentdocs` paths are retired.

GET/api/v1.1/context/manage/doc-typesSecret keySDK

List doc-types

List built-in + org-defined document subtypes from the PG-backed `context_doc_types` registry.

Usage: Discover what `type` values can be passed to `POST /memory/save` with `shape='document'`. Built-ins: `guideline`, `playbook`, `reference`, `template`, `brief`.

SDK: client.v1_1.context.listDocTypes()

Response

{
  "data": {
    "docTypes": [
      { "name": "guideline", "scope": "builtin", "displayName": "Guideline" },
      { "name": "playbook", "scope": "builtin", "displayName": "Playbook" },
      { "name": "icp_brief", "scope": "org", "displayName": "ICP Brief", "createdBy": "user_..." }
    ]
  }
}
POST/api/v1.1/context/manage/doc-typesSecret keySDK

Create doc-type (admin)

Creates an org-defined document subtype (a per-org row; system built-ins are never touched).

Usage: Extend the doc-type registry with org-specific document categories (e.g. `icp_brief`, `pricing_sheet`, `objection_playbook`) for use as `type` on `POST /memory/save` with `shape='document'`.

SDK: client.v1_1.context.createDocType(body)

Parameters

FieldTypeWhereRequiredDescription
type_namestringbodyYesSlug identifier. Must match `^[a-z][a-z0-9_]{2,40}$` (lowercase snake_case; kebab-case is rejected).
labelstringbodyYesDisplay label, 1-80 chars.
descriptionstringbodyNoWhat this type is used for, max 500 chars.
constraint_levelstringbodyNoHow strictly retrieval treats documents of this type.Options: hard, soft, reference
scoring_boostnumberbodyNoRetrieval ranking boost/penalty.Range: -11
always_on_eligiblebooleanbodyNoWhether docs of this type can be included unconditionally in context assembly.
intent_boostsobjectbodyNoMap of intent name to boost value (-1 to 1).
require_rfc2119booleanbodyNoRequire RFC 2119 keywords (MUST/SHOULD/etc.) in docs of this type.
require_examplesbooleanbodyNoRequire at least one example in docs of this type.
min_wordsnumberbodyNoMinimum word count for docs of this type.
max_wordsnumberbodyNoMaximum word count for docs of this type.
preamblestringbodyNoText prepended when this type is assembled into context, max 500 chars.
agent_instructionstringbodyNoInstruction shown to the agent for how to use docs of this type, max 2000 chars.
when_to_usestringbodyNoGuidance on when this type applies, max 1000 chars.
routing_keywordsstring[]bodyNoKeywords used to route queries toward this type. Max 20 entries, 60 chars each.
output_structure_hintstringbodyNoHint for how generated docs of this type should be structured, max 2000 chars.
example_titlesstring[]bodyNoExample document titles for this type. Max 20 entries, 200 chars each.
sort_ordernumberbodyNoDisplay order relative to other types.

Request

{
  "type_name": "icp_brief",
  "label": "ICP Brief",
  "description": "Ideal-customer-profile summaries used in qualification playbooks.",
  "constraint_level": "soft"
}

Response

{
  "type": {
    "org_id": "org_123",
    "type_name": "icp_brief",
    "type_id": "b3f1...",
    "label": "ICP Brief",
    "description": "Ideal-customer-profile summaries used in qualification playbooks.",
    "constraint_level": "soft",
    "scoring_boost": 0,
    "always_on_eligible": false,
    "is_builtin": false
  }
}
  • Requires `admin` scope on the API key.
PATCH/api/v1.1/context/manage/doc-types/:type_nameSecret keySDK

Update doc-type (admin)

Partial update of an org-defined doc-type's metadata. `type_name` is immutable here (use rename); built-ins always return 404.

Usage: Edit a doc-type's label, description, or scoring behavior without renaming it.

SDK: client.v1_1.context.updateDocType(typeName, body)

Parameters

FieldTypeWhereRequiredDescription
type_namestringpathYesDoc-type to update.
labelstringbodyNoNew display label.
descriptionstringbodyNoNew description. `null` clears it.
constraint_levelstringbodyNoUpdate retrieval strictness.Options: hard, soft, reference
scoring_boostnumberbodyNoUpdate the retrieval ranking boost.Range: -11
always_on_eligiblebooleanbodyNoUpdate whether docs of this type can be always-on.
routing_keywordsstring[]bodyNoReplace the routing keywords.
sort_ordernumberbodyNoUpdate display order.

Request

{
  "label": "ICP Brief (v2)",
  "scoring_boost": 0.2
}

Response

{
  "type": {
    "org_id": "org_123",
    "type_name": "icp_brief",
    "label": "ICP Brief (v2)",
    "scoring_boost": 0.2,
    "is_builtin": false
  }
}
  • Requires `admin` scope on the API key.
  • Only per-org rows are editable; a missing type or a system built-in both return 404 (the UPDATE only matches `is_builtin = false` rows).
DELETE/api/v1.1/context/manage/doc-types/:type_nameSecret keySDK

Delete doc-type (admin)

Soft-deletes an org-defined doc-type. Use `?force=orphan` to reassign existing docs to `reference` first. Built-ins cannot be deleted.

Usage: Retire an org-defined doc-type that is no longer needed.

SDK: client.v1_1.context.deleteDocType(typeName)

Parameters

FieldTypeWhereRequiredDescription
type_namestringpathYesDoc-type to delete.
forcestringqueryNoSet to `orphan` to reassign existing docs to `reference` and proceed with deletion.Options: orphan

Response

{
  "error": "type_in_use",
  "doc_count": 12,
  "options": ["reassign_to", "merge_via_rename", "orphan_with_default"],
  "hint": "Pass ?force=orphan to reassign all docs to \"reference\" and delete, or use the rename endpoint to merge into another type."
}
  • Requires `admin` scope on the API key.
  • 204 No Content on success. A missing type, or a system built-in with no per-org override, returns 404.
  • 409 (`type_in_use`, shown above) if docs still reference this type and `?force=orphan` was not passed.
POST/api/v1.1/context/manage/doc-types/:type_name/renameSecret keySDK

Rename doc-type (admin)

Atomic rename with alias forwarding -- the old name becomes a tombstone that continues to resolve to the renamed type for backwards compatibility.

Usage: Evolve the type taxonomy (or merge one org type into another) without breaking existing document references.

SDK: client.v1_1.context.renameDocType(name, { newName })

Parameters

FieldTypeWhereRequiredDescription
type_namestringpathYesCurrent doc-type name.
new_namestringbodyYesNew slug. Must match `^[a-z][a-z0-9_]{2,40}$` and differ from `type_name`.

Request

{ "new_name": "icp_profile" }

Response

{
  "type": {
    "org_id": "org_123",
    "type_name": "icp_profile",
    "label": "ICP Brief",
    "is_builtin": false
  }
}
  • Requires `admin` scope on the API key.
  • 403 (`builtin_immutable`) if `type_name` is a system built-in, 404 (`not_found`) if there is no per-org row with that name, 409 (`target_name_in_use`) if `new_name` already exists and is active.
GET/api/v1.1/context/manage/doc-types/:type_name/historySecret keySDK

Doc-type audit log

Returns the append-only change history for a doc-type (create/update/rename/delete events), newest first.

Usage: Audit who changed a doc-type's definition and when.

SDK: client.v1_1.context.getDocTypeHistory(typeName)

Parameters

FieldTypeWhereRequiredDescription
type_namestringpathYesDoc-type to fetch history for.
limitnumberqueryNoPage size.Default: 50

Response

{
  "entries": [
    { "history_id": "hist_1", "org_id": "org_123", "type_name": "icp_brief", "change_kind": "updated", "change_summary": null, "actor_user_id": "user_123", "diff": { "scoring_boost": [0, 0.2] }, "created_at": "2026-08-20T10:00:00.000Z" }
  ]
}
  • Read access only requires the API key (no `admin` scope), unlike the mutation routes.
GET/api/v1.1/context/manage/tagsSecret keySDK

List curated tags

Returns the org's curated tag vocabulary: system built-ins merged with per-org overrides. Mirrors the doc-types list pattern.

Usage: Discover what canonical tags exist before tagging a context document, or before building a tag-picker UI.

SDK: client.v1_1.context.listTags(opts)

Parameters

FieldTypeWhereRequiredDescription
include_ai_legacystringqueryNoSet to `true` to also surface AI-created legacy tags (`created_by='ai-legacy'`), hidden by default so they can be triaged separately.Options: true

Response

{
  "tags": [
    { "org_id": "org_123", "canonical_tag": "sales", "tag_id": "tag_1", "label": "Sales", "description": null, "aliases": ["selling"], "usage_count": 42, "renamed_to": null, "is_builtin": true, "is_active": true, "sort_order": 0, "created_by": null, "created_at": "2026-01-01T00:00:00.000Z", "updated_at": "2026-01-01T00:00:00.000Z" }
  ]
}
  • System built-ins are omitted for an un-activated org (no kit installed yet), the same way the doc-types list is gated.
GET/api/v1.1/context/manage/tags/:canonical_tag/historySecret keySDK

Tag audit log

Returns the append-only change history for one tag (create/update/rename/delete events), newest first.

Usage: Audit who changed a tag's definition and when.

SDK: client.v1_1.context.getTagHistory(canonicalTag, opts)

Parameters

FieldTypeWhereRequiredDescription
canonical_tagstringpathYesTag to fetch history for.
limitnumberqueryNoPage size.Default: 50

Response

{
  "canonical_tag": "sales",
  "history": [
    { "history_id": "hist_1", "org_id": "org_123", "canonical_tag": "sales", "change_kind": "updated", "change_summary": null, "actor_user_id": "user_123", "diff": { "label": ["Sales Team", "Sales"] }, "created_at": "2026-08-20T10:00:00.000Z" }
  ],
  "count": 1
}
POST/api/v1.1/context/manage/tagsSecret keySDK

Create a tag (admin)

Creates (or upserts) a per-org curated tag. Best-effort embedding generation runs after the write so semantic tag-matching ("snap") picks it up from day one.

Usage: Add an org-specific tag to the curated vocabulary used for context-document categorization and snap-matching.

SDK: client.v1_1.context.createTag(body)

Parameters

FieldTypeWhereRequiredDescription
canonical_tagstringbodyYesSlug identifier. Must match `^[a-z][a-z0-9_-]{1,59}$`.
labelstringbodyYesDisplay label, 1-80 chars.
descriptionstringbodyNoWhat this tag means, max 500 chars. Used to generate the matching embedding.
aliasesstring[]bodyNoAlternate spellings/synonyms that resolve to this tag. Max 20 entries, same slug format as `canonical_tag`.
sort_ordernumberbodyNoDisplay order relative to other tags.

Request

{
  "canonical_tag": "renewal_risk",
  "label": "Renewal Risk",
  "description": "Signals that an account is at risk of not renewing.",
  "aliases": ["churn_risk"]
}

Response

{
  "tag": {
    "org_id": "org_123",
    "canonical_tag": "renewal_risk",
    "tag_id": "tag_9",
    "label": "Renewal Risk",
    "description": "Signals that an account is at risk of not renewing.",
    "aliases": ["churn_risk"],
    "usage_count": 0,
    "renamed_to": null,
    "is_builtin": false,
    "is_active": true,
    "sort_order": 0,
    "created_by": "user_123",
    "created_at": "2026-09-01T00:00:00.000Z",
    "updated_at": "2026-09-01T00:00:00.000Z"
  }
}
  • Requires `admin` scope on the API key.
  • The response never includes the `embedding` field, even though it's stored -- it's a large numeric array with no use to API consumers.
PATCH/api/v1.1/context/manage/tags/:canonical_tagSecret keySDK

Update a tag (admin)

Partial update of a per-org tag's label, description, aliases, or sort order. `canonical_tag` is immutable here (use rename); built-ins always return 404. Re-embeds automatically when `description` changes.

Usage: Edit a tag's metadata without renaming it.

SDK: client.v1_1.context.updateTag(canonicalTag, body)

Parameters

FieldTypeWhereRequiredDescription
canonical_tagstringpathYesTag to update.
labelstringbodyNoNew display label, 1-80 chars.
descriptionstringbodyNoNew description. `null` clears it and triggers a re-embed.
aliasesstring[]bodyNoReplace the alias list. Max 20 entries.
sort_ordernumberbodyNoUpdate display order.

Request

{ "label": "Renewal Risk (Q3)", "aliases": ["churn_risk", "at_risk"] }

Response

{
  "tag": {
    "org_id": "org_123",
    "canonical_tag": "renewal_risk",
    "label": "Renewal Risk (Q3)",
    "aliases": ["churn_risk", "at_risk"],
    "is_builtin": false
  }
}
  • Requires `admin` scope on the API key.
  • Only per-org rows are editable; a missing tag or a system built-in both return 404.
DELETE/api/v1.1/context/manage/tags/:canonical_tagSecret keySDK

Delete a tag (admin)

Soft-deletes a per-org tag. Built-ins or non-existent rows return 404. Does NOT cascade to context documents -- docs keep the tag in their `tags`/`auto_tags` arrays, they just stop matching this tag in future snap suggestions.

Usage: Retire an org-specific tag that is no longer useful.

SDK: client.v1_1.context.deleteTag(canonicalTag)

Parameters

FieldTypeWhereRequiredDescription
canonical_tagstringpathYesTag to delete.
  • Requires `admin` scope on the API key.
  • 204 No Content on success.
POST/api/v1.1/context/manage/tags/:canonical_tag/renameSecret keySDK

Rename a tag (admin)

Atomic rename: the old name becomes a tombstone alias, the new name carries the preserved `tag_id`. Documents referencing the old name keep working via the alias chain.

Usage: Evolve the tag vocabulary without breaking documents that already carry the old tag.

SDK: client.v1_1.context.renameTag(canonicalTag, { newName })

Parameters

FieldTypeWhereRequiredDescription
canonical_tagstringpathYesCurrent tag name.
new_namestringbodyYesNew slug. Must match `^[a-z][a-z0-9_-]{1,59}$`.

Request

{ "new_name": "churn_risk" }

Response

{
  "tag": {
    "org_id": "org_123",
    "canonical_tag": "churn_risk",
    "tag_id": "tag_9",
    "label": "Renewal Risk",
    "is_builtin": false
  }
}
  • Requires `admin` scope on the API key.
  • 403 (`builtin_immutable`) for a system built-in, 404 (`not_found`) if there is no per-org row with that name, 409 (`target_name_in_use`) if `new_name` already exists and is active.
POST/api/v1.1/context/save/batchSecret keySDK

Async bulk doc save (1-24h SLA)

Document-specific async bulk save via provider batch APIs (Anthropic Message Batches / OpenAI Batches / Bedrock CreateModelInvocationJob). 1-24h SLA. Returns `eventId` immediately. Tier-dependent max: basic 50 / pro 250 / enterprise 1000 docs.

Usage: Use for large knowledge migrations where 1-24h latency is acceptable in exchange for ~50% cost reduction. Webhook `context.batch-save.completed` is the preferred completion signal.

SDK: client.v1_1.context.save.batch()

Parameters

FieldTypeWhereRequiredDescription
itemsobject[]bodyYesArray of doc-save payloads (same shape as `POST /memory/save` with `shape='document'`).
optionsobjectbodyNo`{ tier, callbackUrl }`. Provider auto-selected by org BYOK config.

Response

{
  "eventId": "evt_batch_...",
  "status": "queued",
  "estimatedCompletionMs": 7200000,
  "itemCount": 150,
  "cost": { "estimatedCredits": 1800, "tier": "pro", "savingsVsSync": "~50%" }
}
  • Tier-dependent max: basic 50 / pro 250 / enterprise 1000 documents per call.
  • Webhook `context.batch-save.completed` fires when the batch reaches terminal state.
  • Use `/context/save/batch/validate` (free) for pre-flight validation and cost estimate.
  • The same handler is also mounted at the legacy path `POST /context/batch-save` (word order swapped) -- identical behavior, same request/response shape.
POST/api/v1.1/context/save/batch/validateSecret keySDK

Validate bulk doc save (no charge)

Pre-flight validation — returns `{ valid, itemCount, estimatedCredits }` or per-item errors. No credits charged.

Usage: Validate payload + estimate cost before committing to a billable batch run.

SDK: client.v1_1.context.save.batchValidate()

  • Also mounted at the legacy path `POST /context/batch-save/validate` -- identical handler.
GET/api/v1.1/context/save/batch/:eventId/statusSecret keySDK

Poll bulk doc save status

Returns batch status and per-item results once terminal. Prefer the `context.batch-save.completed` webhook over polling.

Usage: Status check fallback when webhooks aren't wired.

SDK: client.v1_1.context.save.batchStatus(eventId)

  • Also mounted at the legacy path `GET /context/batch-save/:eventId/status` -- identical handler.
GET/api/v1.1/contextSecret keySDK

List Context Docs

Lists all agent-readable documents with optional type and tag filtering.

Usage: Use this to list all context docs or filter by type (guideline, playbook, reference, template, brief). Old path /api/v1.1/agentdocs is RETIRED in v1.1 (returns 410 Gone).

SDK: client.context.list()

Parameters

FieldTypeWhereRequiredDescription
typestringqueryNoFilter by AgentDoc type.Options: guideline, playbook, reference, template, brief
tagsstring | string[]queryNoOnly include these tags.
excludeTagsstring | string[]queryNoExclude these tags.
recordIdstringqueryNoFilter/boost docs linked to this record.
limitnumberqueryNoPage size.Default: 20
nextTokenstringqueryNoCursor from a previous page.
summarybooleanqueryNoOmit full content from response.

Response

{
  "success": true,
  "data": {
    "actions": [
      {
        "id": "act_123",
        "type": "variables",
        "payload": {
          "name": "Sales Qualification SOP",
          "agentDocType": "playbook",
          "description": "Step-by-step qualification process",
          "tags": ["sales", "qualification"]
        }
      }
    ],
    "count": 1
  }
}

SDK Example

const docs = await client.context.list({ type: 'playbook', tags: ['sales'] });
POST/api/v1.1/contextSecret keySDK

Create Context Doc

Creates a new agent-readable document. Set type to control how agents treat it.

Usage: Create guidelines (enforceable rules), playbooks (step-by-step processes), references (background info), templates (output formats), or briefs (account context). Old path /api/v1.1/agentdocs remains as a stable alias.

SDK: client.context.create()

Parameters

FieldTypeWhereRequiredDescription
namestringbodyYesDocument name.
typestringbodyNoAgentDoc type.Default: referenceOptions: guideline, playbook, reference, template, brief
valuestringbodyNoMarkdown content.
descriptionstringbodyNoShort description for search/routing.
tagsstring[]bodyNoCategorization tags.
recordIdsstring[]bodyNoAssociated record IDs.

Request

{
  "name": "Sales Qualification SOP",
  "type": "playbook",
  "value": "## Step 1: Initial Contact\n...",
  "description": "Step-by-step sales qualification process",
  "tags": ["sales", "qualification"]
}

Response

{
  "success": true,
  "data": { "id": "act_456", "type": "variables", ... }
}

SDK Example

const doc = await client.context.create({
  name: 'Sales Qualification SOP',
  type: 'playbook',
  value: '## Step 1\n...',
  tags: ['sales'],
});
POST/api/v1.1/context/retrieveSecret keySDK

Retrieve Context Docs (canonical v1.1)

Canonical route for AI-powered doc routing. Semantically selects relevant context docs for a task with optional type filtering. Identical behavior to POST /ai/smart-docs.

Usage: Use this canonical path for new integrations. POST /ai/smart-docs and POST /api/v1.1/agentdocs/retrieve remain as stable aliases.

SDK: client.context.retrieve()

Parameters

FieldTypeWhereRequiredDescription
messagestringbodyYesTask description with rich keywords.
typesstring[]bodyNoFilter by types.Options: guideline, playbook, reference, template, brief
modestringbodyNoRouting mode.Default: autoOptions: fast, deep, auto
tagsstring[]bodyNoInclude only docs with these tags.
maxContentTokensnumberbodyNoToken budget limit.Default: 10000

Request

{
  "message": "Writing cold outreach to VP Sales at healthcare company",
  "types": ["guideline", "template"],
  "mode": "fast"
}

Response

{
  "success": true,
  "data": {
    "selection": [
      { "name": "Email Compliance Rules", "priority": "critical", "content": "..." }
    ],
    "compiledContext": "[GUIDELINE] SOURCE: Email Compliance Rules\n..."
  }
}
  • Canonical path added in v1.1. POST /ai/smart-docs and POST /api/v1.1/agentdocs/retrieve remain as stable aliases.
POST/api/v1.1/context/saveSecret keySDK

Save Context Doc with AI (canonical v1.1)

Save content as a context doc. AI-powered (default) or direct storage (aiExtraction=false). Supports per-token billing, pipeline presets, and multi-file upload.

Usage: Use this canonical path for new integrations. POST /smart-update and POST /api/v1.1/agentdocs/save remain as stable aliases.

SDK: client.context.save()

Parameters

FieldTypeWhereRequiredDescription
typestringbodyNoTarget AgentDoc type.Options: guideline, playbook, reference, template, brief
instructionstringbodyYesWhat to do with the material, or document title for direct storage.
materialstringbodyNoContent to save (up to 800K chars).
strategystringbodyNoExecution strategy.Default: suggestOptions: suggest, safe, force
aiExtractionbooleanbodyNoSet false to store content without AI. 0 credits. Default: true.
tierstringbodyNoPricing tier.Options: basic, pro, ultra
pipelinePresetstringbodyNoPipeline preset for accuracy/cost tradeoff.Options: fast, standard, thorough

Request

// AI-powered (default)
{
  "type": "guideline",
  "instruction": "Update our cold email policy with these new compliance rules",
  "material": "New regulations require...",
  "strategy": "suggest"
}

// Direct storage (no AI, 0 credits)
{
  "instruction": "Q2 Meeting Notes",
  "material": "## Decisions\nApproved budget...",
  "aiExtraction": false
}

Response

{
  "success": true,
  "data": {
    "plan": [
      { "action": "update", "guidelineId": "act_123", "name": "Cold Email Policy", "changes": "..." }
    ]
  }
}
  • Canonical path added in v1.1. POST /smart-update and POST /api/v1.1/agentdocs/save remain as stable aliases.
GET/api/v1.1/context/manage/downloadSecret key

Download context docs as a zip

Builds (or serves a cached) zip of the org's context docs, including their attachments, and returns a presigned S3 download URL. Same handler that backs `GET /guidelines/download`, but this path applies no type filter by default -- it covers every doc type.

Usage: Export context docs -- content, metadata, and attachments -- as a portable zip archive, for example for backup or migration.

Parameters

FieldTypeWhereRequiredDescription
typestringqueryNoRestrict the zip to one AgentDoc type.Options: guideline, playbook, reference, template, brief

Response

{
  "success": true,
  "url": "https://...presigned-s3-url...",
  "scope": "org",
  "type": "all",
  "count": 37,
  "cached": false
}
  • Also reachable at the legacy alias `GET /context/download` (no `/manage`) -- same handler, same behavior.
  • Results are cached per org+type for a TTL; `cached: true` on a cache hit.
  • The zip includes each doc's content, a `meta.json`, and its attachments, plus a top-level `manifest.json`.
  • Returns `url: null, count: 0` (still 200) when the org has no docs, or none matching the `type` filter.
POST/api/v1.1/context/manage/:id/cloneSecret key

Clone a context doc

Creates a duplicate of an existing context document (guideline, playbook, reference, template, or brief) under the caller's organization, copying its payload verbatim.

Usage: Branch off an existing doc as a starting point for a new one without hand-copying its content.

Parameters

FieldTypeWhereRequiredDescription
idstringpathYesContext doc ID to clone.

Request

POST /api/v1.1/context/manage/act_abc123/clone

Response

{
  "success": true,
  "message": "Action cloned successfully",
  "data": {
    "id": "3f9a1c2e-...-uuid",
    "type": "variables",
    "organizationId": "org_abc123",
    "payload": { "name": "Sales Qualification SOP", "agentDocType": "playbook", "value": "..." },
    "createdAt": "2026-09-01T00:00:00.000Z"
  }
}
  • Also reachable at the legacy alias `POST /context/:id/clone` (no `/manage`) -- same handler, same behavior; both are one operation at two paths.
  • The clone's `payload` is copied as-is; the name is NOT suffixed with "(copy)" or similar -- the source and clone share the same displayed name.
  • The cloned doc gets a fresh raw UUID as its `id`, not the `act_`-prefixed format `POST /context` (create) generates.
  • Requires the caller to have clone access to the source doc; returns 403 (`Access Denied`) otherwise, 404 if the source doc doesn't exist.
POST/api/v1.1/context/manage/:id/attachments/batchSecret key

Batch upload context doc attachments

Uploads multiple file attachments to a context document in one multipart request, each with its own metadata. Same validation, storage, and LLM audit pipeline as a single-file upload -- the same route factory used for guideline attachments, scoped to a context doc instead.

Usage: Attach several resources (scripts, templates, configs) to a context document in one call instead of one request per file.

Parameters

FieldTypeWhereRequiredDescription
idstringpathYesContext doc ID.
filesfile[]bodyYesUp to 10 files (multipart/form-data field name `files`).
manifestobject[]bodyYesJSON array of per-file metadata, same index order as `files`. Each entry: `{ type, description, usage, name?, language?, sectionHeader? }`.

Request

POST /api/v1.1/context/manage/act_abc123/attachments/batch
Content-Type: multipart/form-data

files: <binary>, <binary>
manifest: [{ "type": "reference", "description": "...", "usage": "..." }, { "type": "config", "description": "...", "usage": "..." }]

Response

{ "success": true, "data": { "uploaded": [{ "id": "att_a1b2c3", "name": "..." }], "warnings": [] } }
  • Returns 201 on success.
  • Also reachable at the legacy alias `POST /context/:id/attachments/batch` (no `/manage`) -- same handler.
  • Same operation, quota, and validation as `POST /guidelines/{id}/attachments/batch` -- see the Governance category -- just scoped to a context document instead of a guideline.
  • No SDK wrapper for this scope yet; `client.context.*` only exposes `listAttachments` / `getAttachment` / `deleteAttachment`.

1 endpoints

Agents And Evaluation

Evaluate memorization quality against a schema.

POST/api/v1.1/evaluate/memorization-accuracySecret keySDK

Memorization accuracy evaluation

Runs extraction, analysis, and schema-optimization phases against a collection.

Usage: Use this to test whether your schema captures the right information from real-world text.

SDK: client.evaluate.memorizationAccuracy()

Parameters

FieldTypeWhereRequiredDescription
collectionIdstringbodyYesCollection under test.
inputstringbodyYesText to evaluate.

Request

{
  "collectionId": "col_contact",
  "input": "Jane is the VP of Sales at Acme and needs SOC 2 docs before buying.",
  "skipStorage": true
}

Response

{
  "success": true,
  "data": {
    "success": true,
    "phases": [
      { "phase": "extraction", "collectionName": "Contact Properties" },
      { "phase": "analysis" },
      { "phase": "schema" }
    ],
    "summary": {
      "totalDuration": 4400,
      "propertiesOptimized": 1
    }
  }
}

SDK Example

const result = await client.evaluate.memorizationAccuracy({
  collectionId: "col_contact",
  input: "Jane is the VP of Sales at Acme and needs SOC 2 docs before buying.",
  skipStorage: true,
});
console.log(result.data.summary);

7 endpoints

Usage And Key Management

Inspect usage and manage API keys. These routes are intended for authenticated application or dashboard contexts.

GET/api/v1.1/usage/currentJWT

Current month usage

Returns usage for the current month for one organization.

Usage: Use this to power billing dashboards, usage alerts, or internal admin views.

Parameters

FieldTypeWhereRequiredDescription
organizationIdstringqueryYesOrganization to inspect.

Response

{
  "success": true,
  "data": {
    "organizationId": "org_123",
    "month": "202603",
    "usage": {
      "apiCalls": 128,
      "credits": 42
    }
  }
}
POST/api/v1.1/keysJWT

Create an API key

Creates a new secret key for an organization.

Usage: Use this from your dashboard or internal tooling when you need to provision integration credentials.

Parameters

FieldTypeWhereRequiredDescription
organizationIdstringbodyYesOwning organization ID.
descriptionstringbodyYesHuman-readable label.
scopestringbodyNoKey scope.Default: adminOptions: admin, member-only, read-only
expiresInnumber | 'never'bodyNoExpiry in days or `never`.Default: never

Request

{
  "organizationId": "org_123",
  "description": "Production CRM sync",
  "scope": "admin",
  "expiresIn": 90
}

Response

{
  "success": true,
  "data": {
    "id": "key_123",
    "apiKey": "sk_live_...",
    "scope": "admin",
    "organizationId": "org_123"
  },
  "warning": "Save this key now. It cannot be retrieved again."
}
GET/api/v1.1/keysJWT

List API keys

Lists redacted API keys for an organization.

Usage: Use this to render key inventories, rotation reminders, or governance dashboards.

Parameters

FieldTypeWhereRequiredDescription
organizationIdstringqueryYesOrganization to inspect.

Response

{
  "success": true,
  "data": {
    "keys": [
      {
        "id": "key_123",
        "description": "Production CRM sync",
        "scope": "admin",
        "keyPrefix": "sk_live_abcd"
      }
    ]
  }
}
GET/api/v1.1/keys/:idJWT

Get one API key

Returns the decrypted key plus owner metadata for one key ID.

Usage: Use this sparingly for internal admin flows that need to reveal or verify one key record.

Parameters

FieldTypeWhereRequiredDescription
idstringpathYesKey ID.
organizationIdstringqueryYesOwning organization ID.

Response

{
  "success": true,
  "data": {
    "id": "key_123",
    "apiKey": "sk_live_...",
    "organizationId": "org_123",
    "userId": "user_123",
    "scope": "admin"
  }
}
POST/api/v1.1/keys/:id/regenerateJWT

Regenerate a key

Rotates an existing key and returns the new plain-text secret once.

Usage: Use this for key rotation workflows or incident response.

Parameters

FieldTypeWhereRequiredDescription
idstringpathYesKey ID.
organizationIdstringbodyYesOwning organization ID.

Request

{
  "organizationId": "org_123"
}

Response

{
  "success": true,
  "data": {
    "newApiKey": "sk_live_new_...",
    "oldRevokedAt": "2026-03-18T12:00:00.000Z"
  },
  "warning": "Save this key now. It cannot be retrieved again."
}
DELETE/api/v1.1/keys/:idJWT

Revoke a key

Revokes an API key immediately.

Usage: Use this when a key is no longer needed or should be invalidated after a security event.

Parameters

FieldTypeWhereRequiredDescription
idstringpathYesKey ID.
organizationIdstringqueryYesOwning organization ID.

Response

{
  "success": true,
  "message": "API key revoked"
}
GET / POST/api/v1.1/keys/validateNone

Validate a secret key

Resolves a secret key to its organization, user, key ID, and scope. No prior authentication required.

Usage: Use this to verify a secret key from external systems without requiring JWT auth.

Parameters

FieldTypeWhereRequiredDescription
AuthorizationBearer sk_live_...headerNoPreferred way to send the key.
apiKeystringbodyNoPOST fallback for sending the key in the body.

Response

{
  "success": true,
  "data": {
    "organizationId": "org_123",
    "userId": "user_123",
    "keyId": "key_123",
    "scope": "admin"
  }
}
  • This is the only public endpoint that requires no prior authentication.

12 endpoints

Organizations & Members

Manage your organization and team members. API keys are strictly scoped to one organization.

GET/api/v1.1/organizationsSecret keySDK

Get current organization

Returns the organization associated with the calling API key. Each key is scoped to exactly one org.

Usage: Use this to verify which org your API key is connected to and check org metadata.

SDK: client.organizations.get()

Response

{
  "id": "org_abc123",
  "name": "Acme Corp",
  "owner": "user_xyz",
  "numberOfMembers": 3,
  "createdAt": "2026-01-15T10:00:00Z",
  "updatedAt": "2026-03-20T14:30:00Z"
}
  • Free -- no credit cost.
POST/api/v1.1/organizationsSecret keySDK

Create organization

Creates a new organization with the calling user as owner. Auto-generates an admin API key for the new org, returned in the response (shown only once).

Usage: Use this for programmatic org provisioning. The new key is scoped to the new org only.

SDK: client.organizations.create({ name })

Parameters

FieldTypeWhereRequiredDescription
namestringbodyYesOrganization name (1-100 characters).

Request

{ "name": "Acme Corp" }

Response

{
  "organization": {
    "id": "org_new123",
    "name": "Acme Corp",
    "owner": "user_xyz",
    "numberOfMembers": 1,
    "createdAt": "2026-04-06T10:00:00Z"
  },
  "apiKey": {
    "id": "key_abc",
    "key": "sk_live_xxxx...",
    "scope": "admin",
    "organizationId": "org_new123",
    "note": "Store securely. This key is shown only once."
  }
}
  • Free. Rate limited to 5 creates per hour per API key.
PATCH/api/v1.1/organizationsSecret keySDK

Update organization

Updates the organization name. Caller must be the organization owner.

Usage: Use this to rename your organization.

SDK: client.organizations.update({ name })

Parameters

FieldTypeWhereRequiredDescription
namestringbodyYesNew organization name (1-100 characters).

Request

{ "name": "Acme Corp Updated" }

Response

{
  "id": "org_abc123",
  "name": "Acme Corp Updated",
  "owner": "user_xyz",
  "numberOfMembers": 3,
  "updatedAt": "2026-04-06T12:00:00Z"
}
  • Free. Owner only.
GET/api/v1.1/membersSecret keySDK

List members

Lists all members of the API key's organization with their roles and join dates.

Usage: Use this to see who has access to the organization.

SDK: client.members.list()

Parameters

FieldTypeWhereRequiredDescription
limitnumberqueryNoMax results per page.Default: 25
nextTokenstringqueryNoPagination cursor.

Response

{
  "members": [
    { "userId": "user_xyz", "email": "alice@acme.com", "role": "OWNER", "joinedAt": "2026-01-15T10:00:00Z" }
  ],
  "total": 3,
  "nextToken": null
}
  • Free.
POST/api/v1.1/members/inviteSecret keySDK

Invite members

Invites users by email (max 10 per call). Existing users get an in-app invitation. Non-users get an external invitation claimed on signup.

Usage: Use this to programmatically add team members to your organization.

SDK: client.members.invite({ emails })

Parameters

FieldTypeWhereRequiredDescription
emailsstring[]bodyYesEmail addresses to invite (1-10).
suppressEmailbooleanbodyNoSkip sending invitation email.Default: false

Request

{ "emails": ["alice@acme.com", "bob@acme.com"] }

Response

{
  "invited": [{ "email": "alice@acme.com", "userId": "user_123", "invitationId": "inv_456" }],
  "invitedExternal": [{ "email": "bob@acme.com", "invitationId": "inv_789" }],
  "alreadyMembers": [],
  "alreadyInvited": []
}
  • Free. Admin scope required.
DELETE/api/v1.1/members/:userIdSecret keySDK

Remove member

Removes a member from the organization. Owner can remove any non-owner. Non-owner can only remove themselves (leave). Cannot target the owner.

Usage: Use this to remove team members or leave an organization.

SDK: client.members.remove(userId)

Parameters

FieldTypeWhereRequiredDescription
userIdstringpathYesUser ID to remove.

Response

{ "deleted": true }
  • Free. Cannot target the organization owner -- returns 403.
PATCH/api/v1.1/members/:userId/roleSecret keySDK

Update member role

Changes a member's role between ADMIN and MEMBER. Owner only. Cannot change the owner's role.

Usage: Use this to promote or demote team members.

SDK: client.members.updateRole(userId, { role })

Parameters

FieldTypeWhereRequiredDescription
userIdstringpathYesUser ID to update.
rolestringbodyYesNew role.Options: ADMIN, MEMBER

Request

{ "role": "ADMIN" }

Response

{
  "userId": "user_abc123",
  "role": "ADMIN",
  "updatedAt": "2026-09-01T00:00:00.000Z"
}
  • Free. Owner only. Cannot change the owner's role.
GET/api/v1.1/members/invitationsSecret keySDK

List pending invitations

Lists pending invitations for the organization: in-app invitations (for existing users) and external invitations (for people without an account yet, claimed on signup).

Usage: Audit or display outstanding invites before resending or canceling them.

SDK: client.members.listInvitations()

Response

{
  "invitations": [
    { "email": "bob@acme.com", "userId": null, "invitationId": "inv_789", "createdAt": "2026-08-01T00:00:00.000Z" }
  ],
  "count": 1
}
  • Admin scope required, and the caller must be the organization owner -- 403 (`FORBIDDEN`) otherwise.
  • The underlying service supports `limit` / `nextToken` pagination, but this route does not pass query params through -- it always returns the first page at the service default page size.
GET/api/v1.1/organizations/embedding-configSecret keySDK

Get BYO embedding model config

Returns the org's configured bring-your-own embedding model (1536-dimension only), or `embeddingConfig: null` if the org still uses the platform default.

Usage: Check whether the org has already locked in a custom embedding model before attempting to set one -- the setter is immutable after first use.

SDK: client.organizations.getEmbeddingConfig()

Response

{
  "success": true,
  "status": "locked",
  "editable": false,
  "embeddingConfig": {
    "provider": "openai",
    "model": "text-embedding-3-small",
    "dimensions": 1536,
    "lockedAt": "2026-06-01T00:00:00.000Z"
  }
}
  • `status` is `"default"` (with `embeddingConfig: null`) until the org sets a model for the first time; it then flips to `"locked"` and `editable` becomes `false`.
  • Requires a plan that allows custom LLM keys -- 403 (`byok_not_allowed`) otherwise.
GET/api/v1.1/organizations/embedding-modelsSecret keySDK

List 1536d embedding models

Lists the curated catalog of 1536-dimension-capable embedding models available for the org's first-set picker, across all BYO-selectable providers.

Usage: Show which provider/model combos are eligible before calling the setter -- the platform stores all vectors in one fixed 1536d space.

SDK: client.organizations.listEmbeddingModels()

Parameters

FieldTypeWhereRequiredDescription
includePlatformbooleanqueryNoInclude the keyless platform-default provider (Bedrock) in the list.Default: false

Response

{
  "success": true,
  "dimensions": 1536,
  "providers": ["openai", "openrouter"],
  "data": [{ "provider": "openai", "model": "text-embedding-3-small" }]
}
  • Curated list -- providers don't self-report embedding dimensions; the live probe in `PUT /organizations/embedding-config` is the actual hard guard.
  • Requires a plan that allows custom LLM keys.
GET/api/v1.1/organizations/embedding-models/:providerSecret keySDK

List 1536d embedding models for one provider

Same catalog as the unfiltered list route, scoped to a single provider.

Usage: Fetch just one provider's model options, for example when the UI picker already knows which provider the customer chose.

SDK: client.organizations.listEmbeddingModels(provider)

Parameters

FieldTypeWhereRequiredDescription
providerstringpathYesEmbedding provider.Options: openai, openrouter, bedrock

Response

{
  "success": true,
  "provider": "openai",
  "dimensions": 1536,
  "data": [{ "provider": "openai", "model": "text-embedding-3-small" }]
}
  • 400 (`unsupported_provider`) for a provider outside `openai` / `openrouter` / `bedrock`, with the valid list returned in `supportedProviders`.
PUT/api/v1.1/organizations/embedding-configSecret keySDK

Set BYO embedding model config

Sets the org's bring-your-own embedding model. Immutable after the first successful set for a given org -- the model itself can never change afterward, only the stored API key can be rotated.

Usage: Opt an org into its own embedding-model key instead of the platform default, before any vectors are written under it.

SDK: client.organizations.setEmbeddingConfig({ provider, model, apiKey })

Parameters

FieldTypeWhereRequiredDescription
providerstringbodyYesEmbedding provider.Options: openai, openrouter, bedrock
modelstringbodyYesModel identifier for the chosen provider.
apiKeystringbodyNoRequired for `openai` / `openrouter`. Omit for `bedrock`, which uses the platform's AWS credentials.

Request

{ "provider": "openai", "model": "text-embedding-3-small", "apiKey": "sk-..." }

Response

{
  "success": true,
  "dimensions": 1536,
  "embeddingConfig": {
    "provider": "openai",
    "model": "text-embedding-3-small",
    "dimensions": 1536,
    "lockedAt": "2026-06-01T00:00:00.000Z"
  }
}
  • First set for the org live-probes the model and rejects it (400) unless it returns EXACTLY 1536 dimensions.
  • Immutable lock: calling this again with a DIFFERENT provider/model returns 400 with the currently-locked config in the response -- changing embedding models requires a full re-embed migration, not this endpoint. Calling it again with the SAME provider/model is allowed and only rotates the stored API key.
  • `POST /organizations/embedding-config` is a same-handler alias of this endpoint, kept so the MCP client (which has no PUT verb) can reach it.
  • Requires `admin` scope and a plan that allows custom LLM keys (403 `byok_not_allowed` otherwise).

8 endpoints

Platform Configuration

Manage entity types, MCP servers, and webhook destinations.

GET/api/v1.1/entities/:idSecret keySDK

Get entity type

Returns a single entity type by ID with its full schema definition.

Usage: Use this to inspect an entity type's configuration.

SDK: client.entityTypes.get(id)

Parameters

FieldTypeWhereRequiredDescription
idstringpathYesEntity type ID.

Response

{
  "id": "act_abc123",
  "name": "Contact",
  "slug": "contact",
  "primaryKeyField": "email",
  "isSystem": true,
  "status": "Active",
  "schemaVersion": 1
}
  • Free.
PATCH/api/v1.1/entities/:idSecret keySDK

Update entity type

Updates entity type fields. Cannot change slug or isSystem. System entity types cannot be archived.

Usage: Use this to modify entity type metadata like name, icon, or color.

SDK: client.entityTypes.update(id, { name, description })

Parameters

FieldTypeWhereRequiredDescription
idstringpathYesEntity type ID.
namestringbodyNoDisplay name, 1-100 chars.
pluralLabelstringbodyNoPlural display name, 1-100 chars.
descriptionstringbodyNoDescription (max 500 chars).
iconstringbodyNoIcon identifier (max 50 chars).
colorstringbodyNoHex color, e.g. `#4F46E5`.
primaryKeyFieldstringbodyNoProperty name used as the display/primary key (max 100 chars).
identifierColumnstringbodyNoWhich standard identifier resolves records of this type.Options: email, websiteUrl, phoneNumber, postalCode, deviceId, contentId
statusstringbodyNoActive or Archived. Setting `Archived` on a system type returns 403.Options: Active, Archived

Request

{
  "name": "Company",
  "color": "#4F46E5",
  "description": "A business account."
}

Response

{
  "success": true,
  "data": {
    "id": "act_abc123",
    "name": "Company",
    "pluralLabel": "Companies",
    "slug": "company",
    "description": "A business account.",
    "icon": "building",
    "color": "#4F46E5",
    "primaryKeyField": "websiteUrl",
    "identifierColumn": "websiteUrl",
    "isSystem": false,
    "status": "Active",
    "schemaVersion": 1,
    "createdAt": "2026-01-01T00:00:00.000Z",
    "updatedAt": "2026-09-01T00:00:00.000Z"
  }
}
  • Free. Requires `admin` scope on the API key.
  • `slug` and `isSystem` cannot be changed via this endpoint -- they're stripped from the body before validation even if sent.
  • At least one field is required.
DELETE/api/v1.1/entities/:idSecret keySDK

Archive entity type

Archives an entity type (does not destroy). Records of that type still exist. Reversible via PATCH with status: Active. System types cannot be archived.

Usage: Use this to retire entity types you no longer need.

SDK: client.entityTypes.archive(id)

Parameters

FieldTypeWhereRequiredDescription
idstringpathYesEntity type ID.

Response

{
  "success": true,
  "data": {
    "id": "act_abc123",
    "name": "Company",
    "slug": "company",
    "isSystem": false,
    "status": "Archived",
    "schemaVersion": 1
  },
  "message": "Entity type archived. To restore it, send PATCH /api/v1.1/entities/:id with { \"status\": \"Active\" }."
}
  • This is a soft-delete (archive), not a hard delete -- existing records of this type and their data are untouched, and the type is reversible via `PATCH /entities/:id` with `{ status: 'Active' }`.
  • Free. Requires `admin` scope on the API key. System types (Contact, Company) return 403.
GET/api/v1.1/mcpsSecret keySDK

List MCP servers

Lists all registered MCP server connections (simple MCPs only, not OAuth).

Usage: Use this to see which MCP servers are connected to your organization.

SDK: client.mcps.list()

Response

{
  "items": [{
    "id": "mcp_abc",
    "name": "My Internal Tools",
    "serverUrl": "https://mcp.acme.com/sse?key=***",
    "transportType": "streamable-http",
    "authType": "bearer",
    "status": "active",
    "tools": [{ "name": "search_docs", "description": "Search internal docs" }],
    "disabledTools": []
  }],
  "total": 1
}
  • Free. OAuth MCPs (Google, Microsoft, HubSpot) are excluded.
POST/api/v1.1/mcps/testSecret keySDK

Test MCP connection

Tests a connection to an MCP server without creating a record. Returns discovered tools.

Usage: Use this to validate an MCP server URL before registering it.

SDK: client.mcps.test({ serverUrl, transportType, authType })

Parameters

FieldTypeWhereRequiredDescription
serverUrlstringbodyYesMCP server URL (max 2048 chars). SSRF-protected.
transportTypestringbodyYesTransport type.Options: sse, http, streamable-http
authTypestringbodyYesAuthentication type.Options: bearer, api_key, none
apiKeystringbodyNoAPI key (required when authType is not none).

Response

{
  "connected": true,
  "tools": [{ "name": "search_docs", "description": "Search internal docs" }],
  "toolsCount": 1
}
  • Free. 10 second timeout. Private/internal URLs are blocked (SSRF protection).
POST/api/v1.1/mcpsSecret keySDK

Create MCP server

Registers a new MCP server connection with KMS-encrypted credentials.

Usage: Use this to connect an external MCP server to your organization.

SDK: client.mcps.create({ name, serverUrl, transportType, authType })

Parameters

FieldTypeWhereRequiredDescription
namestringbodyYesDisplay name (1-100 chars).
serverUrlstringbodyYesMCP server URL.
transportTypestringbodyYesTransport type.Options: sse, http, streamable-http
authTypestringbodyYesAuth type.Options: bearer, api_key, none
apiKeystringbodyNoAPI key (required when authType is not none).
descriptionstringbodyNoOptional description.
  • Free. Admin scope required. Server URLs redacted in responses.
GET/api/v1.1/destinationsSecret keySDK

List destinations

Lists all webhook and S3 destinations for the organization.

Usage: Use this to see where events are being delivered.

SDK: client.destinations.list()

Response

{
  "success": true,
  "data": [{
    "id": "dest_abc",
    "name": "My Webhook",
    "type": "webhook",
    "config": { "url": "https://hooks.acme.com/personize", "method": "POST", "secret": "********" },
    "events": ["prompt.completed"],
    "isActive": true,
    "retryPolicy": { "maxRetries": 3, "backoffMs": 1000 }
  }]
}
  • Free. Max 25 destinations per org.
POST/api/v1.1/destinationsSecret keySDK

Create destination

Creates a webhook or S3 destination. Signing secret auto-generated for webhooks (returned only in create response).

Usage: Use this to set up event delivery to external systems.

SDK: client.destinations.create({ name, type, config, events })

Parameters

FieldTypeWhereRequiredDescription
namestringbodyYesDisplay name.
typestringbodyYesDestination type.Options: webhook, s3
configobjectbodyYesType-specific config (url/method/headers for webhook, bucketName/region/prefix for s3).
eventsstring[]bodyYesEvents to subscribe to (e.g., prompt.completed).
  • Free. Admin scope required. SSRF protection on webhook URLs. Signing secret shown only once.

14 endpoints

Analytics

Read-only metrics for monitoring org health, memory performance, and credit usage.

GET/api/v1.1/analytics/overviewSecret keySDK

Organization overview

High-level snapshot of organization health: record counts, memory counts, active destinations and MCPs.

Usage: Use this for AI agent self-monitoring -- quick health check of the org.

SDK: client.analytics.overview()

Response

{
  "records": { "total": 12450 },
  "memories": { "total": 87320 },
  "properties": { "total": 62100, "avgPerRecord": 5.0 },
  "activeDestinations": 3,
  "activeMcps": 2
}
  • 0.1 credits. 5 min cache.
GET/api/v1.1/analytics/memorySecret keySDK

Memory performance

Memorize and recall performance metrics for a time window.

Usage: Use this to monitor memory system health and performance trends.

SDK: client.analytics.memory({ window: '24h' })

Parameters

FieldTypeWhereRequiredDescription
windowstringqueryNoTime window.Default: 24hOptions: 1h, 24h, 7d, 30d

Response

{
  "window": "24h",
  "memorize": { "totalCalls": 1240, "successRate": 0.98, "avgLatencyMs": 320 },
  "recall": { "totalCalls": 890, "successRate": 0.99, "avgLatencyMs": 210 }
}
  • 0.1 credits. 5 min cache.
GET/api/v1.1/analytics/creditsSecret keySDK

Credit balance

Current credit balance, usage breakdown, and subscription details.

Usage: Use this to monitor credit consumption. Always free so agents can check without spending credits.

SDK: client.analytics.credits()

Response

{
  "balance": 48500,
  "included": 100000,
  "used": 51500,
  "purchased": 0,
  "monthly": 100000
}
  • Always free -- no credit cost.
GET/api/v1.1/analytics/operationsSecret keySDK

Operation counts

Operation volume and token usage for a time window.

Usage: Use this to track API usage patterns and token consumption.

SDK: client.analytics.operations({ window: '7d' })

Parameters

FieldTypeWhereRequiredDescription
windowstringqueryNoTime window.Default: 24hOptions: 1h, 24h, 7d, 30d

Response

{
  "window": "7d",
  "operations": { "memorizes": 8700, "recalls": 6230 },
  "tokens": { "input": 2400000, "output": 890000, "total": 3290000 },
  "credits": { "used": 51500 }
}
  • 0.1 credits. 5 min cache.
GET/api/v1.1/analytics/accessSecret keySDK

Analytics access and tier

Returns the caller's resolved analytics tier and which analytics features are unlocked, with the credit cost and minimum tier required for each.

Usage: Use this to pre-render locked/unlocked analytics widgets in your UI instead of discovering access via 403s.

SDK: client.analytics.access()

Response

{
  "orgId": "org_123",
  "tier": "free",
  "tierOrder": ["free", "starter", "pro", "personizer"],
  "isSuperAdmin": false,
  "billInternal": false,
  "features": {
    "access": { "allowed": true, "requiredTier": "free", "credits": 0 },
    "overview": { "allowed": true, "requiredTier": "free", "credits": 0 },
    "operations.breakdown": { "allowed": false, "requiredTier": "starter", "credits": 0.1 },
    "retrieve": { "allowed": false, "requiredTier": "pro", "credits": 0.1 }
  }
}
  • Free discovery endpoint -- no credit charge, no tier requirement. Cached 60s.
  • Feature keys and their tier/credit values are SSM-tunable (`/personize/{stage}/analytics/access`), so exact numbers can change without a code deploy.
GET/api/v1.1/analytics/memory/historySecret keySDK

Memory metrics history

Returns a time series of a single memory metric (latency, throughput, quality, or cost) at hourly or daily granularity.

Usage: Use this to chart memory system trends over time instead of a single snapshot.

SDK: client.analytics.memoryHistory({ metric: 'throughput', granularity: 'daily', days: 7 })

Parameters

FieldTypeWhereRequiredDescription
metricstringqueryNoMetric to chart.Default: throughputOptions: latency, throughput, quality, cost
granularitystringqueryNoBucket size.Default: dailyOptions: hourly, daily
daysnumberqueryNoLookback window in days.Default: 7Range: 190

Response

{
  "orgId": "org_123",
  "metric": "throughput",
  "granularity": "daily",
  "days": 7,
  "dataPoints": [
    { "timestamp": "2026-08-25T00:00:00.000Z", "value": 1240 }
  ]
}
  • Requires Pro tier. Default 0.1 credits (SSM-tunable). 5 min cache.
GET/api/v1.1/analytics/retrieveSecret keySDK

Retrieve latency and feedback

Per-mode retrieve latency (p50/p95/p99), token and cache-hit stats, and thumbs-up/down feedback summary for a time window.

Usage: Use this to monitor retrieve quality and latency by mode, and to track feedback trends from `POST /retrieve/feedback`.

SDK: client.analytics.retrieve({ window: '7d' })

Parameters

FieldTypeWhereRequiredDescription
windowstringqueryNoTime window.Default: 24hOptions: 1h, 24h, 7d, 30d

Response

{
  "window": "7d",
  "status": "ok",
  "modes": {
    "scout": {
      "calls": 420, "ok": 410, "failed": 5, "partial": 5, "successRate": 0.976,
      "p50Ms": 180, "p95Ms": 640, "p99Ms": 1100,
      "totalTokens": 128000, "cacheReadTokens": 40000, "cacheHitRatio": 0.3125,
      "costUSD": 1.42
    },
    "brief": {
      "calls": 96, "ok": 94, "failed": 1, "partial": 1, "successRate": 0.979,
      "p50Ms": 1400, "p95Ms": 3200, "p99Ms": 4100,
      "totalTokens": 210000, "cacheReadTokens": 90000, "cacheHitRatio": 0.4286,
      "costUSD": 3.05
    }
  },
  "feedback": { "total": 38, "thumbsUp": 31, "thumbsDown": 4, "neutral": 3, "thumbsUpRate": 0.8158 }
}
  • Requires Pro tier via the shared analytics access gate, but this handler does not currently deduct credits despite the tier's advertised default. 5 min cache.
  • `status` is `unavailable` (with an `error`) when the underlying PG read fails, or `empty` when the window has no calls.
GET/api/v1.1/analytics/operations/breakdownSecret keySDK

Operation breakdown by type

Grouped, expandable per-type breakdown of operations (calls, credits, tokens, latency) for a time window, rollup-backed.

Usage: Use this to see which specific operation types (not just memorize/retrieve totals) are driving credit spend.

SDK: client.analytics.breakdown({ window: '7d' })

Parameters

FieldTypeWhereRequiredDescription
windowstringqueryNoTime window.Default: 24hOptions: 1h, 24h, 7d, 30d

Response

{
  "window": "7d",
  "source": "rollup",
  "asOf": "2026-09-01T00:00:00.000Z",
  "groups": [
    {
      "group": "memory",
      "label": "Memory",
      "count": 8700,
      "credits": 4200.5,
      "children": [
        { "type": "save", "label": "Save", "count": 6200, "credits": 3100.2, "inputTokens": 1800000, "outputTokens": 420000, "ok": 6100, "failed": 100, "p50Ms": 220, "p95Ms": 900 }
      ]
    }
  ],
  "totals": { "credits": 4200.5, "operations": 8700 }
}
  • Requires Starter tier. Default 0.1 credits (SSM-tunable). 5 min cache.
  • `costUSD` (internal provider cost) is stripped from this org-scoped response; only `credits` (customer-visible cost) is included.
  • p50Ms/p95Ms are the max seen across merged rollup buckets, not a true percentile recomputation.
GET/api/v1.1/analytics/operations/historySecret keySDK

Per-request operation history

Keyset-paginated feed of individual operations (not aggregated), with per-row credits, tokens, latency, and optionally the sanitized request/response payloads.

Usage: Use this to drill into individual calls behind an aggregate spike, or to build a live operations log.

SDK: client.analytics.operationHistory({ limit: 50 })

Parameters

FieldTypeWhereRequiredDescription
limitnumberqueryNoPage size.Default: 50Range: 1200
cursorstringqueryNoOpaque cursor from a previous page's `nextCursor`.
opTypestringqueryNoFilter to one operation type.
clientstringqueryNoFilter by calling client.Options: ui, sdk, cli, mcp, api, unknown
sourcestringqueryNoFilter by call source.Options: api, agent_tool, mcp, scheduler, batch
userIdstringqueryNoFilter to one user.
withPayloadsbooleanqueryNoInclude the sanitized request/response body per row. Set `false` for lighter, larger pages.Default: true

Response

{
  "orgId": "org_123",
  "items": [
    {
      "opId": "op_abc123", "type": "save", "label": "Save", "group": "memory",
      "createdAt": "2026-09-01T00:00:00.000Z",
      "userId": "user_123", "source": "api", "client": "sdk", "status": "ok",
      "creditsUsed": 1.2, "durationMs": 340,
      "inputTokens": 1800, "outputTokens": 420,
      "llmModel": "gpt-4.1", "llmProvider": "openai", "mode": "shortform",
      "request": { "content": "..." }, "response": { "success": true }
    }
  ],
  "nextCursor": "MjAyNi0wOS0wMVQwMDowMDowMC4wMDBafG9wX2FiYzEyMw==",
  "hasMore": true
}
  • Requires Pro tier via the shared analytics access gate, but this is a raw read (no aggregation) and is not billed. Never cached -- always live.
  • `costUSD` (internal provider cost) is stripped from this org-scoped response.
  • `request`/`response` are `null` for rows recorded before this feature shipped, and for non-HTTP operations with no captured body.
GET/api/v1.1/analytics/operations/totalsSecret keySDK

Grand totals

Rollup-backed grand totals (credits, operations, tokens, cost, success rate) for a time window, or org-lifetime when `window` is omitted, plus a compact per-group breakdown.

Usage: Use this for a single KPI-row summary instead of assembling one from the full breakdown.

SDK: client.analytics.totals({ window: '30d' })

Parameters

FieldTypeWhereRequiredDescription
windowstringqueryNoTime window. Omit for org-lifetime totals.Options: 1h, 24h, 7d, 30d

Response

{
  "window": "30d",
  "source": "rollup",
  "asOf": "2026-09-01T00:00:00.000Z",
  "totals": {
    "credits": 51500.2, "operations": 14900,
    "tokens": {
      "input": 2400000, "output": 890000, "total": 3290000,
      "cachedInput": 900000, "freshInput": 1500000, "cacheWrite": 60000
    },
    "successRate": 0.987
  },
  "byGroup": [
    { "group": "memory", "label": "Memory", "count": 8700, "credits": 4200.5 }
  ]
}
  • Free (Free tier, 0 default credits). 5 min cache.
  • `costUSD` (internal provider cost) is stripped from this org-scoped response; it is present on the Super-Admin `/analytics/admin/totals` surface.
GET/api/v1.1/analytics/operations/timeseriesSecret keySDK

Usage over time

Rollup-backed daily/weekly/monthly time series: overall totals plus a per-group series, for charting usage over time.

Usage: Use this to render a usage-over-time chart, optionally split by operation group.

SDK: client.analytics.timeseries({ window: '30d' })

Parameters

FieldTypeWhereRequiredDescription
windowstringqueryNoTime window, or `all` for full history.Default: 30dOptions: 1h, 24h, 7d, 30d, all
granularitystringqueryNoBucket size. Defaults to `day` for a windowed range, `month` for `all`.Options: day, week, month

Response

{
  "window": "30d",
  "granularity": "day",
  "source": "rollup",
  "asOf": "2026-09-01T00:00:00.000Z",
  "series": [
    { "day": "2026-08-25", "credits": 1400.2, "operations": 480, "inputTokens": 320000, "outputTokens": 90000, "ok": 470, "failed": 10 }
  ],
  "byGroup": [
    { "group": "memory", "label": "Memory", "series": [ { "day": "2026-08-25", "credits": 700.1, "calls": 240 } ] }
  ]
}
  • Requires Starter tier. Default 0.1 credits (SSM-tunable). 5 min cache.
GET/api/v1.1/analytics/operations/modelsSecret keySDK

Model and provider cost breakdown

Per-model and per-provider call counts, token usage, and credit cost for a time window.

Usage: Use this to see which models or providers are driving cost, and to spot-check average credits per call.

SDK: client.analytics.models({ window: '30d' })

Parameters

FieldTypeWhereRequiredDescription
windowstringqueryNoTime window.Default: 30dOptions: 1h, 24h, 7d, 30d

Response

{
  "window": "30d",
  "source": "pg",
  "models": [
    { "name": "gpt-4.1", "provider": "openai", "calls": 3200, "credits": 2100.4, "inputTokens": 1400000, "outputTokens": 380000, "tokens": 1780000, "avgCreditsPerCall": 0.66, "creditsPerKToken": 1.1806 }
  ],
  "providers": [
    { "name": "openai", "provider": null, "calls": 3200, "credits": 2100.4, "inputTokens": 1400000, "outputTokens": 380000, "tokens": 1780000, "avgCreditsPerCall": 0.66, "creditsPerKToken": 1.1806 }
  ]
}
  • Requires Pro tier. Default 0.2 credits (SSM-tunable). 5 min cache.
  • `costUSD` (internal provider cost) is stripped from this org-scoped response.
GET/api/v1.1/analytics/operations/inventorySecret keySDK

Org entity inventory

Org-wide entity counts: records, saved memories, filled properties, entity types, collections, graph edges, relation types, graph rules, document types, documents, integrations, and API keys.

Usage: Use this for a single-call snapshot of everything provisioned in an org, for a settings/overview page.

SDK: client.analytics.inventory()

Response

{
  "orgId": "org_123",
  "source": "pg",
  "inventory": {
    "records": 12450, "savedMemories": 87320, "filledProperties": 62100,
    "entityTypes": 3, "collections": 6, "declaredProperties": 48,
    "graphEdges": 21000, "relationTypes": 22, "graphRules": 14,
    "documentTypes": 5, "documents": 40,
    "integrations": { "mcps": 2, "destinations": 3 },
    "apiKeys": 4
  }
}
  • Free (Free tier, 0 default credits). 5 min cache.
  • Individual inventory fields fall back to `null` (not 0) when their sub-lookup fails, so `null` means "couldn't determine" rather than "zero".
POST/api/v1.1/analytics/operations/refreshSecret keySDK

Refresh operations rollup

Triggers an on-demand recompute of this org's operations rollup, rate-limited to once every 5 minutes per organization.

Usage: Use this when you need the totals/breakdown/timeseries endpoints to reflect very recent activity instead of waiting for the next scheduled rollup.

SDK: client.analytics.refresh()

Response

{
  "refreshed": true,
  "asOf": "2026-09-01T00:05:00.000Z"
}
  • No request body or query/path parameters -- the refresh is scoped to the caller's organization via the API key, and there is nothing to configure.
  • Requires Pro tier via the shared analytics access gate, but this handler does not currently deduct credits.
  • If called again within 5 minutes of the last refresh, returns `{ "refreshed": false, "asOf", "retryAfterSeconds", "message" }` instead of re-running.
  • Invalidates this org's cached totals/breakdown so the next read reflects the refresh.

5 endpoints

BYOK Configuration

Bring-your-own-key LLM routing: inspect and update per-function/per-tier model config, browse live provider model catalogs, and verify the org's routing actually resolves onto customer keys instead of managed credentials. These routes exist under v1.1 only -- there is no v1 equivalent.

GET/api/v1.1/byok/llm-configSecret keySDK

Get BYOK configuration

Returns the org's BYOK enablement, stored provider keys (masked), and per-function/per-tier model routing config.

Usage: Use this to render the current BYOK configuration before letting a user edit it.

SDK: client.byok.getConfig()

Response

{
  "success": true,
  "byokEnabled": true,
  "providers": [
    { "provider": "openai", "verified": true, "maskedKey": "sk-...ab12", "addedAt": "2026-04-01T00:00:00.000Z" }
  ],
  "functions": {
    "memorize": {
      "basic": { "provider": "openai", "model": "gpt-4o-mini" },
      "pro": { "provider": "openai", "model": "gpt-4.1" },
      "ultra": null
    },
    "recall": null,
    "smartContext": null,
    "smartUpdate": null,
    "generate": null
  }
}
  • Returns 403 with `code: 'byok_not_allowed'` when the org's plan does not allow custom LLM keys.
PUT/api/v1.1/byok/llm-configSecret keySDK

Save BYOK configuration

Replaces per-function tier model routing. Setting a function to `null` resets it to the platform default; sending a function replaces its entire tier map.

Usage: Use this to point specific functions (memorize, recall, smartContext, smartUpdate, generate) at your own provider keys and models, per tier.

SDK: client.byok.saveConfig({ functions })

Parameters

FieldTypeWhereRequiredDescription
functionsobjectbodyYesPartial map of function name to per-tier model config or `null`. Keys: `memorize`, `recall`, `smartContext`, `smartUpdate`, `generate`. Each value is `{ basic?, pro?, ultra? }` where each tier is `{ provider, model, reasoning? }` or `null`.

Request

{
  "functions": {
    "memorize": {
      "pro": { "provider": "openai", "model": "gpt-4.1" }
    }
  }
}

Response

{
  "success": true,
  "message": "Configuration saved"
}
  • Rejected with 400 (`code: 'byok_invalid_provider'`) if a referenced provider has no stored key for the org.
  • Rejected with 400 (`code: 'invalid_model'`) if a model id is confirmed absent from the provider's live catalog.
  • Provider keys themselves (add/remove) are not managed by this endpoint -- that stays in the Personize dashboard.
GET/api/v1.1/byok/resolved-modelsSecret keySDK

Get resolved BYOK models

Returns the effective (post-routing) provider and model for every BYOK-eligible function, for one tier or all tiers.

Usage: Use this to confirm what actually resolves at request time, since a saved config can still fall back to the platform default under certain conditions.

SDK: client.byok.resolvedModels({ tier })

Parameters

FieldTypeWhereRequiredDescription
tierstringqueryNoTier to resolve.Default: allOptions: basic, pro, ultra, all

Response

{
  "success": true,
  "orgId": "org_123",
  "resolved": {
    "pro": {
      "memorize": { "provider": "openai", "model": "gpt-4.1", "routeReason": "org-byok", "isByok": true },
      "recall": { "provider": "openrouter", "model": "google/gemma-4-31b-it", "routeReason": "platform-tier", "isByok": false }
    }
  }
}
  • `isByok: false` with `routeReason: 'platform-tier'` means that function is currently running on Personize's managed credentials, not the org's own key.
GET/api/v1.1/byok/llm-config/models/:providerSecret keySDK

Get provider model catalog

Fetches the live model catalog for a provider, decrypting the org's stored key server-side when the provider requires one for catalog access.

Usage: Use this to populate a model picker with the provider's actual available models instead of a hardcoded list.

SDK: client.byok.getModels(provider, { refresh })

Parameters

FieldTypeWhereRequiredDescription
providerstringpathYesProvider id, for example `openai`, `anthropic`, `openrouter`, `bedrock`.
refreshbooleanqueryNoBypass the 1-hour catalog cache and refetch live.Default: false

Response

{
  "success": true,
  "provider": "openai",
  "data": [
    { "id": "gpt-4.1", "name": "GPT-4.1" }
  ],
  "cached": true
}
  • Returns 400 (`code: 'no_provider_key'`) if the provider needs a key for catalog access and the org has not stored one.
  • Catalogs are cached in-memory per org+provider for 1 hour unless `refresh=true`.
GET/api/v1.1/byok/verifySecret keySDK

Verify BYOK configuration

Per-entry health check of the stored config: whether each configured (function, tier) entry has a key present, resolves to a valid catalog model, and actually runs on the customer's key rather than falling back to managed credentials.

Usage: Use this to audit BYOK setup and catch entries that silently fall back to managed credentials.

SDK: client.byok.verify()

Response

{
  "success": true,
  "ok": false,
  "entries": [
    {
      "function": "memorize", "tier": "pro", "provider": "openai", "model": "gpt-4.1",
      "keyPresent": true, "modelValid": true, "isByok": true, "routeReason": "org-byok"
    },
    {
      "function": "recall", "tier": "pro", "provider": "openai", "model": "gpt-4.1",
      "keyPresent": false, "modelValid": true, "isByok": false, "routeReason": "platform-tier",
      "error": "Resolves to Personize managed credentials (isByok=false), provider key missing or undecryptable"
    }
  ]
}
  • `ok` is `true` only when every entry both resolves onto the org's own key (or `org-byoc`) and has a valid catalog model.

5 endpoints

Notifications

Send actionable notifications to organization members with link, callback, and dismiss buttons.

POST/api/v1.1/notificationsSecret keySDK

Send notification

Sends notifications to specific users with optional actionable buttons. Supports link, callback (webhook), and dismiss actions.

Usage: Use this for AI agents to surface decisions, alerts, or approvals to humans.

SDK: client.notifications.send({ recipients, title, body, actions })

Parameters

FieldTypeWhereRequiredDescription
recipientsstring[]bodyYesUser IDs to notify (max 50).
titlestringbodyYesNotification title (max 200 chars).
bodystringbodyYesNotification body (max 2000 chars).
prioritystringbodyNoPriority level.Default: normalOptions: normal, urgent
actionsAction[]bodyNoActionable buttons (max 5). Types: link, callback, dismiss.

Request

{
  "recipients": ["userId1"],
  "title": "3 new leads matched your ICP",
  "body": "Found via overnight enrichment pipeline",
  "priority": "normal",
  "actions": [
    { "type": "link", "label": "Review", "url": "/records?filter=new" },
    { "type": "callback", "label": "Approve", "callbackUrl": "https://agent.acme.com/approve", "callbackPayload": { "batchId": "42" } },
    { "type": "dismiss", "label": "Dismiss" }
  ]
}

Response

{
  "success": true,
  "notificationIds": ["notif_abc123"],
  "recipientCount": 1
}
  • 0.5 credits per recipient. Rate limited: 50 per org per hour. Callback URLs are SSRF-protected.
POST/api/v1.1/notifications/broadcastSecret keySDK

Broadcast notification

Sends a notification to all members matching a role group (all, admins, or owners).

Usage: Use this for org-wide announcements from AI agents.

SDK: client.notifications.broadcast({ recipientGroup, title, body })

Parameters

FieldTypeWhereRequiredDescription
recipientGroupstringbodyYesTarget group.Options: all, admins, owners
titlestringbodyYesNotification title (max 200 chars).
bodystringbodyYesNotification body (max 2000 chars).
prioritystringbodyNoPriority level.Default: normalOptions: normal, urgent
actionsAction[]bodyNoActionable buttons (max 5).
  • 0.5 credits per recipient. Rate limited: 50 per org per hour. Max 100 recipients per broadcast.
GET/api/v1.1/notificationsSecret keySDK

List notifications

Lists notifications for the API key's user, newest first. Excludes dismissed notifications.

Usage: Use this to display a notification inbox or check for pending actions.

SDK: client.notifications.list()

Parameters

FieldTypeWhereRequiredDescription
limitnumberqueryNoMax results.Default: 25
nextTokenstringqueryNoPagination cursor.
  • Free.
GET/api/v1.1/notifications/unread-countSecret keySDK

Unread count

Returns the number of unread notifications for the API key's user.

Usage: Use this for badge counts in the dashboard.

SDK: client.notifications.unreadCount()

Response

{ "unreadCount": 5 }
  • Free.
POST/api/v1.1/notifications/:id/actionSecret keySDK

Execute callback action

Executes a callback action on a notification. Fires a signed webhook (HMAC-SHA256) to the callbackUrl with the callbackPayload. Callbacks expire after 7 days.

Usage: Use this to trigger human-in-the-loop approvals from the dashboard.

SDK: client.notifications.executeAction(id, actionId)

Parameters

FieldTypeWhereRequiredDescription
idstringpathYesNotification ID.
actionIdstringbodyYesAction ID from the notification's actions array.

Response

{ "status": "delivered" }
  • Free. Returns 410 if callback expired. Webhook signed with HMAC-SHA256.

6 endpoints

Schedules

Create recurring or one-time tasks that run a prompt or send a notification, on a cron/rate schedule (recurring) or at a fixed future time (one-time). v1 only -- not mounted on v1.1. Reads (list, get, executions) accept `admin`, `member-only`, or `read-only` key scope; writes (create, update, delete) require `admin` scope.

POST/api/v1.1/schedulesSecret keySDK

Create a schedule

Creates a recurring or one-time schedule. Recurring schedules fire on `pattern`; one-time schedules fire once at `runAt`.

Usage: Use this to automate a recurring prompt (e.g. a daily digest) or a one-time reminder without building your own cron infrastructure.

SDK: client.schedules.create(options)

Parameters

FieldTypeWhereRequiredDescription
namestringbodyYesUnique kebab-case name within the org (lowercase letters, digits, hyphens, underscores).
taskTypestringbodyYesWhat the schedule runs.Options: run_prompt, send_notification, run_sync, run_digest, run_usage_analytics
taskPayloadobjectbodyYesTask-specific payload. For `run_prompt`, the same body as `POST /api/v1.1/prompt` (requires `prompt` or `instructions`). For `send_notification`, `{ contentMode: 'static'|'smart', title/body (static) or topic (smart), recipientEmail or recipientUserId, channels, priority }`.
recurringbooleanbodyNo`true` fires on `pattern`; `false` fires once at `runAt`.Default: false
patternstringbodyNoCron or rate expression. Required when `recurring` is `true`.
runAtstringbodyNoISO8601 timestamp to fire once. Required when `recurring` is `false`.
descriptionstringbodyNoFree-text description (max 500 chars).
startDatestringbodyNoISO8601 timestamp. Recurring schedules do not fire before this.
endDatestringbodyNoISO8601 timestamp. Recurring schedules stop firing after this.
timezonestringbodyNoIANA timezone the pattern is evaluated in.Default: UTC
enabledbooleanbodyNoWhether the schedule is active.Default: true
concurrencyPolicystringbodyNoBehavior when the previous run of this schedule is still in flight at the next fire time.Default: allowOptions: allow, skip

Request

{
  "name": "daily-followup-john",
  "taskType": "run_prompt",
  "taskPayload": {
    "prompt": "Draft a 2-line follow-up email for this contact.",
    "memorize": { "recordId": "rec_abc", "type": "Contact" },
    "governedMemory": true,
    "outputs": [{ "name": "email", "required": true }]
  },
  "recurring": true,
  "pattern": "rate(1 day)"
}

Response

{
  "success": true,
  "data": {
    "id": "01J8X8Q1Z6R3N9V6K5Y2W1T4C0",
    "organizationId": "org_123",
    "userId": "user_123",
    "name": "daily-followup-john",
    "description": "",
    "taskType": "run_prompt",
    "taskPayload": { "prompt": "Draft a 2-line follow-up email for this contact.", "memorize": { "recordId": "rec_abc", "type": "Contact" }, "governedMemory": true, "outputs": [{ "name": "email", "required": true }] },
    "recurring": true,
    "pattern": "rate(1 day)",
    "timezone": "UTC",
    "enabled": true,
    "status": "active",
    "concurrencyPolicy": "allow",
    "consecutiveFailures": 0,
    "runCount": 0,
    "errorCount": 0,
    "createdAt": "2026-09-01T00:00:00.000Z",
    "updatedAt": "2026-09-01T00:00:00.000Z"
  }
}
  • Recurring needs `pattern`; one-time needs `runAt` -- never both, the schema rejects a payload that doesn't match `recurring`.
  • `pattern` syntax: `rate(N minutes|hours|days|weeks)` for a fixed interval (e.g. `rate(1 day)`), or `cron(MIN HOUR DOM MON DOW YEAR)` (AWS-flavored 6-field cron, e.g. `cron(0 9 ? * MON *)` for Mondays at 9am) for calendar-based firing.
  • Recurring `rate(...)` schedules with no user-supplied `startDate` get up to ±60s of jitter auto-applied, so many identical 'every N' schedules don't all fire at the same second.
  • `run_prompt` and `send_notification` payloads are validated against their full schema at creation time. `run_sync`, `run_digest`, and `run_usage_analytics` are internal-only task types (migration/platform use).
  • Requires `admin` scope.
GET/api/v1.1/schedulesSecret keySDK

List schedules

Lists schedules for the org, optionally filtered by record, contact, company, or task type.

Usage: Use this to see what's currently scheduled, or to find the schedules tied to one record.

SDK: client.schedules.list(options)

Parameters

FieldTypeWhereRequiredDescription
limitnumberqueryNoPage size.Default: 50
nextTokenstringqueryNoCursor from a previous page.
recordIdstringqueryNoFilter to schedules whose taskPayload references this record.
emailstringqueryNoFilter to schedules whose taskPayload references this contact email.
websiteUrlstringqueryNoFilter to schedules whose taskPayload references this company website.
taskTypestringqueryNoFilter by task type.

Response

{
  "success": true,
  "data": [
    { "id": "01J8...", "name": "daily-followup-john", "taskType": "run_prompt", "recurring": true, "pattern": "rate(1 day)", "status": "active", "enabled": true }
  ],
  "nextToken": null
}
  • The recordId/email/websiteUrl/taskType filters are applied after the page is fetched, so a filtered page can return fewer than `limit` items even when more matches exist -- keep following `nextToken`. When any filter is set, the response also includes `filtered: true` and `rawCount` (the unfiltered page size).
  • Requires `admin`, `member-only`, or `read-only` scope.
GET/api/v1.1/schedules/:idSecret keySDK

Get a schedule

Fetches one schedule by its ULID id or kebab-case name.

Usage: Use this to inspect a schedule's current config, status, or last-run info.

SDK: client.schedules.get(idOrName)

Parameters

FieldTypeWhereRequiredDescription
idstringpathYesULID id or kebab-case name.

Response

{
  "success": true,
  "data": {
    "id": "01J8X8Q1Z6R3N9V6K5Y2W1T4C0",
    "name": "daily-followup-john",
    "taskType": "run_prompt",
    "recurring": true,
    "pattern": "rate(1 day)",
    "status": "active",
    "enabled": true,
    "lastRunAt": "2026-08-31T00:00:12.000Z",
    "lastRunStatus": "success",
    "runCount": 42,
    "errorCount": 0
  }
}
  • Returns 404 (`schedule_not_found`) if the id/name doesn't resolve to a schedule in this org, or if it was soft-deleted.
  • Requires `admin`, `member-only`, or `read-only` scope.
PATCH/api/v1.1/schedules/:idSecret keySDK

Update a schedule

Partial update of a schedule. `name`, `taskType`, `recurring`, and `startDate` are immutable after creation.

Usage: Use this to change a schedule's payload, timing, or enabled state without recreating it.

SDK: client.schedules.update(idOrName, patch)

Parameters

FieldTypeWhereRequiredDescription
idstringpathYesULID id or kebab-case name.
descriptionstringbodyNoNew description (max 500 chars).
taskPayloadobjectbodyNoReplace the task payload.
patternstringbodyNoNew cron/rate expression (recurring schedules).
runAtstringbodyNoNew fire time (one-time schedules).
endDatestringbodyNoNew end date.
timezonestringbodyNoNew IANA timezone.
enabledbooleanbodyNoEnable or disable the schedule.
concurrencyPolicystringbodyNoNew concurrency policy.Options: allow, skip

Request

{ "pattern": "rate(4 hours)" }

Response

{
  "success": true,
  "data": { "id": "01J8X8Q1Z6R3N9V6K5Y2W1T4C0", "name": "daily-followup-john", "pattern": "rate(4 hours)", "enabled": true }
}
  • Requires `admin` scope.
DELETE/api/v1.1/schedules/:idSecret keySDK

Delete a schedule

Soft-deletes a schedule (90-day retention). After deletion, GET/PATCH/DELETE and the executions endpoint all 404 for this schedule.

Usage: Use this to stop a schedule from firing and remove it from listings.

SDK: client.schedules.delete(idOrName)

Parameters

FieldTypeWhereRequiredDescription
idstringpathYesULID id or kebab-case name.

Response

{
  "success": true,
  "deleted": "daily-followup-john"
}
  • Requires `admin` scope.
GET/api/v1.1/schedules/:id/executionsSecret keySDK

List execution history

Lists past executions for one schedule, newest first.

Usage: Use this to audit whether a scheduled task actually ran, and inspect its result or error per firing.

SDK: client.schedules.executions(idOrName, options)

Parameters

FieldTypeWhereRequiredDescription
idstringpathYesULID id or kebab-case name of the schedule.
limitnumberqueryNoPage size.Default: 20
nextTokenstringqueryNoCursor from a previous page.

Response

{
  "success": true,
  "data": [
    {
      "id": "01J9...",
      "scheduleId": "01J8X8Q1Z6R3N9V6K5Y2W1T4C0",
      "organizationId": "org_123",
      "taskType": "run_prompt",
      "status": "success",
      "startedAt": "2026-08-31T00:00:00.000Z",
      "completedAt": "2026-08-31T00:00:12.000Z",
      "durationMs": 12000,
      "destinationsDispatched": true
    }
  ],
  "nextToken": null
}
  • Returns 404 (`schedule_not_found`) if the schedule doesn't exist in this org or was soft-deleted.
  • Requires `admin`, `member-only`, or `read-only` scope.

16 endpoints

Integrations

Configure and run CRM/CSV sync as reusable DataSources: field mappings resolved manually, from a template, or via AI matching, plus on-demand runs and recurring schedules.

POST/api/v1.1/integrations/datasourcesSecret keySDK

Create a DataSource

Creates a sync config (a DataSource) that maps a CRM or CSV provider's fields to Personize properties. Mappings are resolved manually, from a built-in or saved template, or via AI field matching, depending on `mode`.

Usage: Use this to set up a sync between a connected CRM (or CSV upload) and Personize memory, choosing how field mappings get resolved.

SDK: client.integrations.datasources.create(options)

Parameters

FieldTypeWhereRequiredDescription
namestringbodyYesDisplay name for the DataSource.
providerstringbodyYesSource system.Options: hubspot, salesforce, apollo, apollo-oauth, csv
entityTypestringbodyYesTarget Personize entity type (e.g. `contact`, `company`).
modestringbodyNoHow `propertyMappings` gets resolved.Default: manualOptions: manual, template, ai
propertyMappingsPropertyMapping[]bodyNoRequired and non-empty when `mode='manual'`. Each item: `{ source | staticValue, target, direction?, extractMemories?, aiGenerated? }`.
templateobjectbodyNoRequired when `mode='template'`. One of `{ type: 'builtin', id }`, `{ type: 'user', id }`, or `{ type: 'collections', collectionIds }`.
aiobjectbodyNoUsed when `mode='ai'`. `{ collectionIds?, minConfidence? (default 0.4), fallbackToTemplate? (default true) }`.

Request

{
  "name": "HubSpot Contacts",
  "provider": "hubspot",
  "entityType": "contact",
  "mode": "template",
  "template": { "type": "builtin", "id": "hubspot_contacts_standard" }
}

Response

{
  "success": true,
  "data": {
    "id": "ds_01j...",
    "organizationId": "org_123",
    "createdAt": "2026-09-01T00:00:00.000Z",
    "payload": {
      "name": "HubSpot Contacts",
      "provider": "hubspot",
      "entityType": "contact",
      "direction": "in",
      "mappingMode": "manual",
      "propertyMappings": [...],
      "status": "active"
    },
    "mode": "template",
    "template": { "type": "builtin", "id": "hubspot_contacts_standard" }
  }
}

SDK Example

const ds = await client.integrations.datasources.create({
  name: 'HubSpot Contacts',
  provider: 'hubspot',
  entityType: 'contact',
  mode: 'template',
  template: { type: 'builtin', id: 'hubspot_contacts_standard' },
});
await client.integrations.datasources.run(ds.data!.id, { direction: 'in' });
  • `mode='template'` or `mode='ai'` (managed mappings) automatically get an identity field and the CRM's native record id appended to `propertyMappings` when the template or matcher didn't already include them, so writeback and dedupe always resolve to a real key. `mode='manual'` is stored exactly as authored.
  • Preview what a template or AI match would produce first with `POST /integrations/mapping/suggest`, without creating a DataSource.
GET/api/v1.1/integrations/datasourcesSecret keySDK

List DataSources

Lists the org's DataSource configs, newest first.

Usage: Use this to see what syncs are configured for the org.

SDK: client.integrations.datasources.list(options)

Parameters

FieldTypeWhereRequiredDescription
limitnumberqueryNoPage size.Default: 50

Response

{
  "success": true,
  "data": [
    {
      "id": "ds_01j...",
      "organizationId": "org_123",
      "createdAt": "2026-09-01T00:00:00.000Z",
      "updatedAt": "2026-09-01T00:00:00.000Z",
      "payload": { "name": "HubSpot Contacts", "provider": "hubspot", "entityType": "contact", "direction": "in", "status": "active" }
    }
  ]
}
GET/api/v1.1/integrations/datasources/:idSecret keySDK

Get a DataSource

Fetches one DataSource config by id, including its full mapping payload.

Usage: Use this to inspect a DataSource's current mappings and settings before editing or running it.

SDK: client.integrations.datasources.get(id)

Parameters

FieldTypeWhereRequiredDescription
idstringpathYesThe DataSource id.

Response

{
  "success": true,
  "data": {
    "id": "ds_01j...",
    "organizationId": "org_123",
    "createdAt": "2026-09-01T00:00:00.000Z",
    "payload": { "name": "HubSpot Contacts", "provider": "hubspot", "entityType": "contact", "direction": "in", "propertyMappings": [...], "status": "active" }
  }
}
  • Returns 404 (`not_found`) if the id doesn't resolve to a DataSource in this org.
PATCH/api/v1.1/integrations/datasources/:idSecret keySDK

Update a DataSource

Partially updates a DataSource's mappings or settings.

Usage: Use this to change field mappings, direction, schedule, or status without recreating the DataSource.

SDK: client.integrations.datasources.patch(id, options)

Parameters

FieldTypeWhereRequiredDescription
idstringpathYesThe DataSource id.
namestringbodyNoNew display name.
directionstringbodyNoNew sync direction.Options: in, out, bidirectional
identityFieldsobjectbodyNoReplace the identity field configuration.
mappingModestringbodyNoNew mapping mode.Options: manual, all
propertyMappingsPropertyMapping[]bodyNoReplace the mapping array.
writeStrategystringbodyNoNew sync-out behavior.Options: upsert, create_only, update_only
onNoMatchstringbodyNoNew no-match behavior.Options: create, skip
conflictPolicystringbodyNoNew conflict resolution policy.Options: personize_wins, crm_wins
maxRecordsnumberbodyNoNew per-run record cap.
filtersobjectbodyNoNew provider-side filter criteria.
scheduleEnabledbooleanbodyNoEnable or disable the recurring sync.
scheduleFrequencystringbodyNoNew cadence.Options: hourly, daily, weekly, manual-only
statusstringbodyNoNew status.Options: active, paused, error, retrying

Request

{ "scheduleEnabled": true, "scheduleFrequency": "daily" }

Response

{
  "success": true,
  "data": {
    "id": "ds_01j...",
    "organizationId": "org_123",
    "payload": { "name": "HubSpot Contacts", "scheduleEnabled": true, "scheduleFrequency": "daily", "status": "active" }
  }
}
  • This is a shallow merge over the existing payload, not a full replace.
  • If the DataSource has managed mappings (created with `mode='template'` or `mode='ai'`) and you replace `propertyMappings`, the identity and native-id floor is automatically reapplied so writeback doesn't silently lose the CRM record id.
  • Returns 404 (`not_found`) if the id doesn't resolve to a DataSource in this org.
DELETE/api/v1.1/integrations/datasources/:idSecret keySDK

Delete a DataSource

Removes a DataSource config.

Usage: Use this to stop and remove a sync you no longer need.

SDK: client.integrations.datasources.delete(id)

Parameters

FieldTypeWhereRequiredDescription
idstringpathYesThe DataSource id.

Response

204 No Content
  • Removes the DataSource config only. It does not revoke or disconnect the underlying CRM OAuth connection.
  • Returns 404 (`not_found`) if the id doesn't resolve to a DataSource in this org.
POST/api/v1.1/integrations/datasources/:id/runsSecret keySDK

Run a DataSource

Triggers a sync run for one DataSource, either a real run or a dry-run preview.

Usage: Use this to pull records in from the CRM, push Personize changes out, or validate what a run would do before touching the provider.

SDK: client.integrations.datasources.run(id, options)

Parameters

FieldTypeWhereRequiredDescription
idstringpathYesThe DataSource id.
directionstringbodyYesWhich way to sync.Options: in, out, both
dryRunbooleanbodyNoValidate and preview without touching the provider.Default: false

Request

{ "direction": "in", "dryRun": false }

Response

{
  "success": true,
  "data": {
    "dataSourceId": "ds_01j...",
    "direction": "in",
    "runId": "run_01j...",
    "queuedAt": "2026-09-01T00:00:00.000Z",
    "pollAt": "/api/v1.1/integrations/datasources/ds_01j.../runs",
    "note": "Run dispatched. Poll GET .../runs to watch runId reach a terminal status; the run row's eventId feeds GET .../runs/{eventId} for sampleRecords."
  }
}
  • `direction='in'` imports from the CRM into Personize; `'out'` writes Personize data back to the CRM; `'both'` runs sync-in inline, then schedules a delayed sync-out follow-up.
  • A real run (`dryRun: false`) returns 202 immediately and dispatches async. `runId` is present for `direction='in'` or `'both'`; poll `GET .../runs` to watch it reach a terminal status, then use its `eventId` with `GET .../runs/:eventId` for the sampleRecords preview.
  • `dryRun: true` returns 200 synchronously with a preview (`provider`, `entityType`, `mappingMode`, `propertyMappingsCount`, `writeStrategy`, `onNoMatch`, `conflictPolicy`) and touches nothing. No records are fetched or pushed.
GET/api/v1.1/integrations/datasources/:id/runsSecret keySDK

List DataSource runs

Lists run history for one DataSource, newest first.

Usage: Use this to check whether a sync completed and to grab the eventId for a detailed run report.

SDK: client.integrations.datasources.listRuns(id, options)

Parameters

FieldTypeWhereRequiredDescription
idstringpathYesThe DataSource id.
limitnumberqueryNoPage size.Default: 20

Response

{
  "success": true,
  "data": [
    {
      "runId": "run_01j...",
      "dataSourceId": "ds_01j...",
      "status": "completed",
      "direction": "in",
      "provider": "hubspot",
      "entityType": "contact",
      "triggerType": "manual",
      "startedAt": "2026-09-01T00:00:00.000Z",
      "completedAt": "2026-09-01T00:00:12.000Z",
      "recordsFetched": 120,
      "recordsPushed": 0,
      "recordsFailed": 0,
      "eventId": "evt_01j..."
    }
  ]
}
  • A row's `eventId`, when present, chains to `GET .../runs/:eventId` for that run's sampleRecords.
GET/api/v1.1/integrations/datasources/:id/runs/:eventIdSecret keySDK

Get run detail

Fetches the audit row(s) for one run, including a small sample-records preview.

Usage: Use this to debug a run: per-direction counts, timing, first error, and a preview of the records that were processed.

SDK: client.integrations.datasources.getRun(id, eventId)

Parameters

FieldTypeWhereRequiredDescription
idstringpathYesThe DataSource id.
eventIdstringpathYesThe run's event id, from `listRuns` or the run-dispatch response.

Response

{
  "success": true,
  "data": [
    {
      "eventId": "evt_01j...",
      "organizationId": "org_123",
      "dataSourceId": "ds_01j...",
      "provider": "hubspot",
      "entityType": "contact",
      "direction": "in",
      "triggerType": "manual",
      "status": "completed",
      "startedAt": "2026-09-01T00:00:00.000Z",
      "completedAt": "2026-09-01T00:00:12.000Z",
      "durationMs": 12000,
      "recordCount": 120,
      "successCount": 118,
      "failedCount": 2,
      "skippedCount": 0,
      "sampleRecords": [...]
    }
  ]
}
  • Returns one audit row per direction, so a `'both'` run can return two rows.
  • Returns 404 (`not_found`) if there's no audit row for that event yet.
GET/api/v1.1/integrations/datasources/:id/scheduleSecret keySDK

Get DataSource schedule

Returns a focused view of the DataSource's recurring-sync schedule.

Usage: Use this to check whether a DataSource is on a recurring schedule and when it last ran in each direction.

SDK: client.integrations.datasources.getSchedule(id)

Parameters

FieldTypeWhereRequiredDescription
idstringpathYesThe DataSource id.

Response

{
  "success": true,
  "data": {
    "dataSourceId": "ds_01j...",
    "enabled": true,
    "frequency": "daily",
    "lastSyncInAt": "2026-09-01T02:00:00.000Z",
    "lastSyncOutAt": null
  }
}
PUT/api/v1.1/integrations/datasources/:id/scheduleSecret keySDK

Set DataSource schedule

Enables, disables, or re-tunes the recurring sync for one DataSource.

Usage: Use this to put a DataSource on a recurring cadence instead of running it manually each time.

SDK: client.integrations.datasources.setSchedule(id, options)

Parameters

FieldTypeWhereRequiredDescription
idstringpathYesThe DataSource id.
enabledbooleanbodyYesTurn the recurring sync on or off.
frequencystringbodyNoCadence. Required to enable.Options: hourly, daily, weekly, manual-only

Request

{ "enabled": true, "frequency": "daily" }

Response

{
  "success": true,
  "data": { "dataSourceId": "ds_01j...", "enabled": true, "frequency": "daily", "lastSyncInAt": null, "lastSyncOutAt": null }
}
  • Enabling a schedule (`enabled: true`) requires a real `frequency`: `hourly`, `daily`, or `weekly`. Passing `enabled: true` with `frequency` omitted or `'manual-only'` returns 400.
  • One underlying schedule exists per (org, frequency) bucket, firing on a fixed cadence (top of the hour / 02:00 UTC / Monday 02:00 UTC), not at a caller-chosen time.
DELETE/api/v1.1/integrations/datasources/:id/scheduleSecret keySDK

Disable DataSource schedule

Disables the recurring sync for one DataSource.

Usage: Use this to stop a DataSource from firing on its schedule while keeping the DataSource itself.

SDK: client.integrations.datasources.deleteSchedule(id)

Parameters

FieldTypeWhereRequiredDescription
idstringpathYesThe DataSource id.

Response

{
  "success": true,
  "data": { "dataSourceId": "ds_01j...", "enabled": false, "frequency": "daily", "lastSyncInAt": "2026-09-01T02:00:00.000Z", "lastSyncOutAt": null }
}
  • Disables the recurring sync but keeps the last frequency saved, so re-enabling later is a one-field `PUT`.
GET/api/v1.1/integrations/templatesSecret keySDK

List mapping templates

Lists built-in and org-saved mapping templates, optionally filtered by provider and entity type.

Usage: Use this to find a starting-point mapping before creating a DataSource with `mode='template'`.

SDK: client.integrations.templates.list(options)

Parameters

FieldTypeWhereRequiredDescription
providerstringqueryNoFilter by provider.
entityTypestringqueryNoFilter by entity type.

Response

{
  "success": true,
  "data": {
    "builtIns": [{ "id": "hubspot_contacts_standard", "kind": "builtin", "provider": "hubspot", "entityType": "contact" }],
    "user": []
  }
}
POST/api/v1.1/integrations/templatesSecret keySDK

Save a mapping template

Saves a reusable, org-scoped mapping template.

Usage: Use this to turn a set of field mappings you've tuned once into a reusable template for future DataSources.

SDK: client.integrations.templates.save(options)

Parameters

FieldTypeWhereRequiredDescription
namestringbodyYesTemplate name.
providerstringbodyYesSource system this template applies to.
entityTypestringbodyYesTarget Personize entity type.
propertyMappingsPropertyMapping[]bodyYesNon-empty mapping array.
descriptionstringbodyNoTemplate description.
defaultDirectionstringbodyNoDefault sync direction for DataSources created from this template.Options: in, out, bidirectional
defaultIdentityFieldstringbodyNoDefault identity field for DataSources created from this template.

Request

{
  "name": "My HubSpot Mapping",
  "provider": "hubspot",
  "entityType": "contact",
  "defaultDirection": "in",
  "propertyMappings": [
    { "source": "email", "target": "email", "direction": "both" },
    { "source": "jobtitle", "target": "Job Title", "direction": "in" }
  ]
}

Response

{
  "success": true,
  "data": { "id": "tpl_01j...", "kind": "user", "name": "My HubSpot Mapping", "provider": "hubspot", "entityType": "contact", "propertyMappings": [...] }
}
GET/api/v1.1/integrations/templates/:idSecret keySDK

Get a mapping template

Fetches one template by id.

Usage: Use this to inspect a template's mappings before applying it.

SDK: client.integrations.templates.get(id)

Parameters

FieldTypeWhereRequiredDescription
idstringpathYesBuilt-in template id (e.g. `hubspot_contacts_standard`) or a saved template's UUID; both are tried.

Response

{
  "success": true,
  "data": { "id": "hubspot_contacts_standard", "kind": "builtin", "provider": "hubspot", "entityType": "contact", "propertyMappings": [...] }
}
  • Returns 404 (`not_found`) if neither a built-in nor a saved template matches the id.
DELETE/api/v1.1/integrations/templates/:idSecret keySDK

Delete a mapping template

Deletes a saved (user) template.

Usage: Use this to remove a template you no longer need.

SDK: client.integrations.templates.delete(id)

Parameters

FieldTypeWhereRequiredDescription
idstringpathYesThe saved template's id.

Response

204 No Content
  • Built-in templates cannot be deleted; returns 400 (`cannot_delete_builtin`).
POST/api/v1.1/integrations/mapping/suggestSecret keySDK

Suggest field mappings

Resolves the `propertyMappings` a template or AI match would produce for a provider and entity type, without creating a DataSource.

Usage: Use this to preview and edit suggested mappings before committing to a DataSource, or to build a review step in your own UI.

SDK: client.integrations.suggestMappings(options)

Parameters

FieldTypeWhereRequiredDescription
providerstringbodyYesSource system.
entityTypestringbodyYesTarget Personize entity type.
modestringbodyYesResolution strategy. `manual` is not accepted here.Options: template, ai
templateobjectbodyNoRequired when `mode='template'`. Same shape as in `POST /integrations/datasources`.
aiobjectbodyNoUsed when `mode='ai'`. `{ collectionIds?, minConfidence?, fallbackToTemplate? }`.
csvColumnsstring[]bodyNoRequired only when `provider='csv'` with a `collections` template or `mode='ai'`.

Request

{
  "provider": "hubspot",
  "entityType": "contact",
  "mode": "template",
  "template": { "type": "builtin", "id": "hubspot_contacts_standard" }
}

Response

{
  "success": true,
  "data": {
    "mode": "ai",
    "provider": "hubspot",
    "entityType": "contact",
    "propertyMappings": [...],
    "defaults": { "direction": "in", "identityField": "email" }
  }
}
  • `mode='manual'` returns 400 (nothing to suggest, the caller already has mappings).
  • Does not create a DataSource. Review or edit the returned `propertyMappings`, then pass them to `POST /integrations/datasources` with `mode='manual'`.

3 endpoints

CRM Passthrough

Proxy raw REST calls to a customer's connected CRM using the org's managed OAuth connection, so the caller never has to store or handle CRM credentials directly.

POST/api/v1.1/crm/hubspot/passthroughSecret keySDK

HubSpot passthrough

Forwards one REST call to the HubSpot API using the org's connected HubSpot integration. Personize authenticates the call with the connection made once in the dashboard, so the caller never handles a HubSpot token.

Usage: Use this for HubSpot endpoints not covered by the SDK's typed wrappers, or when you want raw control over the request.

SDK: client.hubspot.request(opts)

Parameters

FieldTypeWhereRequiredDescription
methodstringbodyYesHTTP method to forward.Options: GET, POST, PATCH, PUT, DELETE
pathstringbodyYesHubSpot API path, must start with `/`. Must start with one of: /crm/, /marketing/, /cms/, /automation/, /files/, /communication-preferences/, /properties/, /owners/, /oauth/.
queryobjectbodyNoQuery string parameters.
bodyobjectbodyNoRequest body forwarded as-is (ignored for GET/HEAD).
headersobjectbodyNoCaller headers to forward. Only `idempotency-key`, `if-match`, `if-none-match`, and `accept-language` are allowlisted; anything else is dropped.
timeoutMsnumberbodyNoPer-call timeout.

Request

{
  "method": "GET",
  "path": "/crm/v3/objects/contacts",
  "query": { "limit": 10 }
}

Response

{
  "status": 200,
  "headers": { "x-hubspot-ratelimit-daily-remaining": "39500" },
  "body": { "results": [...] },
  "meta": {
    "provider": "hubspot",
    "upstreamRequestId": "abc-123",
    "durationMs": 210,
    "rateLimit": { "remaining": 39500 }
  }
}

SDK Example

const raw = await client.hubspot.request({ method: 'GET', path: '/owners/v2/owners' });
// Typed wrappers over this same endpoint:
const page = await client.hubspot.contacts.list({ limit: 10 });
  • The response envelope is `{ status, headers, body, meta }`, not the `{ success, data }` shape used elsewhere in this API. `status` and `body` mirror HubSpot's actual response.
  • Errors use `{ error: { code, message }, requestId }` instead of `{ success: false }`. `connection_not_found` and `connection_disconnected` (both 409) mean the org needs to (re)connect HubSpot in the dashboard.
  • `path` is validated against an allowlist of HubSpot API prefixes; anything else is rejected with 400 (`invalid_path`).
  • The SDK also exposes typed wrappers over this same endpoint: `client.hubspot.contacts`, `.companies`, `.deals`, `.tasks`, `.notes`.
POST/api/v1.1/crm/salesforce/passthroughSecret keySDK

Salesforce passthrough

Forwards one REST call to the Salesforce API using the org's Nango-managed Salesforce connection. Personize resolves the instance URL and access token, so the caller never handles Salesforce credentials.

Usage: Use this for Salesforce endpoints not covered by the SDK's typed wrappers, or when you want raw control over the request.

SDK: client.salesforce.request(opts)

Parameters

FieldTypeWhereRequiredDescription
methodstringbodyYesHTTP method to forward.Options: GET, POST, PATCH, PUT, DELETE
pathstringbodyYesSalesforce API path, must start with `/`. Must start with one of: /services/data/, /services/apexrest/.
queryobjectbodyNoQuery string parameters.
bodyobjectbodyNoRequest body forwarded as-is (ignored for GET/HEAD).
headersobjectbodyNoCaller headers to forward. Only `idempotency-key`, `if-match`, `if-none-match`, and `accept-language` are allowlisted; anything else is dropped.
timeoutMsnumberbodyNoPer-call timeout.

Request

{
  "method": "GET",
  "path": "/services/data/v59.0/query",
  "query": { "q": "SELECT Id, Name FROM Account LIMIT 10" }
}

Response

{
  "status": 200,
  "headers": { "sforce-limit-info": "api-usage=15/15000" },
  "body": { "totalSize": 10, "records": [...] },
  "meta": {
    "provider": "salesforce",
    "upstreamRequestId": "abc-123",
    "durationMs": 180,
    "rateLimit": { "remaining": 14985 }
  }
}

SDK Example

const result = await client.salesforce.query('SELECT Id, Name FROM Account LIMIT 10');
for await (const lead of client.salesforce.queryAll('SELECT Id FROM Lead')) { /* ... */ }
await client.salesforce.sobject('Account').create({ Name: 'Acme' });
  • The response envelope is `{ status, headers, body, meta }`, not the `{ success, data }` shape used elsewhere in this API.
  • Errors use `{ error: { code, message }, requestId }` instead of `{ success: false }`. `connection_not_found` and `connection_disconnected` (both 409) mean the org needs to (re)connect Salesforce in the dashboard.
  • `path` is validated against an allowlist of Salesforce API prefixes; anything else is rejected with 400 (`invalid_path`).
  • The SDK also exposes typed wrappers over this same endpoint: `client.salesforce.query()`, `.queryAll()`, `.sobject(type)`.
POST/api/v1.1/crm/apollo-oauth/passthroughSecret key

Apollo passthrough

Forwards one REST call to the Apollo.io API using the org's Apollo OAuth connection (Nango-managed). Same request, response, and error contract as the HubSpot and Salesforce passthroughs.

Usage: Use this to call Apollo.io endpoints using the org's connected Apollo account, without the caller handling an Apollo API key.

Parameters

FieldTypeWhereRequiredDescription
methodstringbodyYesHTTP method to forward.Options: GET, POST, PATCH, PUT, DELETE
pathstringbodyYesApollo API path, must start with `/v1/`.
queryobjectbodyNoQuery string parameters.
bodyobjectbodyNoRequest body forwarded as-is (ignored for GET/HEAD).
headersobjectbodyNoCaller headers to forward. Only `idempotency-key`, `if-match`, `if-none-match`, and `accept-language` are allowlisted; anything else is dropped.
timeoutMsnumberbodyNoPer-call timeout.

Request

{
  "method": "GET",
  "path": "/v1/auth/health"
}

Response

{
  "status": 200,
  "headers": {},
  "body": { "is_logged_in": true },
  "meta": {
    "provider": "apollo-oauth",
    "upstreamRequestId": null,
    "durationMs": 140
  }
}
  • No SDK wrapper exists for Apollo. There is no `client.apollo.*` namespace; call this endpoint directly over HTTP with your secret key.
  • The response envelope is `{ status, headers, body, meta }`, not the `{ success, data }` shape used elsewhere in this API.
  • Errors use `{ error: { code, message }, requestId }` instead of `{ success: false }`. `connection_not_found` and `connection_disconnected` (both 409) mean the org needs to (re)connect Apollo in the dashboard.

5 endpoints

Billing

Look up the platform's per-call credit rates, check the current credit balance from a billing-specific path, and manage low-balance alert thresholds.

GET/api/v1.1/billing/ratesSecret keySDK

Get credit rates

Returns the live, SSM-backed credit rate sheet (per-operation costs) so customers can budget calls without inferring pricing from 402 responses.

Usage: Use this to build cost estimates or a pre-flight budget check before running billable operations.

SDK: client.billing.rates()

Response

{
  "success": true,
  "data": {
    "rates": {
      "version": "...",
      "memorize": { "basic": "...", "pro": "...", "pro_fast": "...", "ultra": "..." },
      "recall": { "fast": "...", "deep": "..." },
      "smartContext": { "fast": "...", "deep": "..." },
      "similar": "...",
      "segment": "...",
      "smartUpdate": "..."
    },
    "memorizeTierConfig": {...},
    "generatedAt": "2026-09-01T00:00:00.000Z",
    "source": "ssm-backed (refreshed every 10 min server-side)"
  }
}
  • Free to call, no credit deduction.
  • Response is cached for 5 minutes (`Cache-Control: private, max-age=300`).
  • BYOK rates are surfaced with a `_byok` suffix on the relevant keys, so you can compare managed vs. BYOK pricing.
GET/api/v1.1/billing/creditsSecret keySDK

Credit balance (billing alias)

Alias of the Analytics category's Credit balance endpoint, kept under `/billing/*` because that's where customers look for it. Served by the exact same handler as `GET /analytics/credits`, so the response is identical.

Usage: Use this path if you're already integrating against the billing surface and don't want to also wire up the analytics endpoint for the same data.

SDK: client.billing.credits()

  • Identical response payload to the Analytics category's Credit balance endpoint (`GET /analytics/credits`); see that entry for the full response shape.
  • Always free, no credit cost.
  • Requires `admin`, `member-only`, or `read-only` scope.
POST/api/v1.1/billing/thresholdsSecret keySDK

Create a credit threshold alert

Registers a credit-balance alert threshold. When the org's available credits cross below the given percent, Personize emits a `CreditThresholdCrossed` event delivered to your configured webhook destinations (HMAC-signed).

Usage: Use this to get notified before an org runs out of credits, instead of discovering it from a 402 response.

SDK: client.billing.thresholds.create(input)

Parameters

FieldTypeWhereRequiredDescription
percentnumberbodyYesThreshold as a whole-number percent of the credit balance.Range: 1100

Request

{ "percent": 20 }

Response

{
  "success": true,
  "data": { "thresholdId": "thr_01j...", "percent": 20, "createdAt": "2026-09-01T00:00:00.000Z" }
}
  • `percent` must be a whole number (integer) between 1 and 100; other values return 400.
  • Each threshold fires at most once per billing cycle and resets the next month.
  • Pair this with `POST /destinations` to set up a webhook receiver for the `CreditThresholdCrossed` event.
GET/api/v1.1/billing/thresholdsSecret keySDK

List credit threshold alerts

Lists the org's registered credit threshold alerts.

Usage: Use this to see what low-balance alerts are currently configured.

SDK: client.billing.thresholds.list()

Response

{
  "success": true,
  "data": {
    "thresholds": [
      { "thresholdId": "thr_01j...", "percent": 20, "createdAt": "2026-09-01T00:00:00.000Z" }
    ],
    "count": 1
  }
}
  • Thresholds are returned sorted by `percent` descending (highest threshold first).
DELETE/api/v1.1/billing/thresholds/:idSecret keySDK

Delete a credit threshold alert

Unregisters a credit threshold alert.

Usage: Use this to stop getting notified at a threshold you no longer care about.

SDK: client.billing.thresholds.delete(id)

Parameters

FieldTypeWhereRequiredDescription
idstringpathYesThe `thresholdId` returned by create or list.

Response

{
  "success": true,
  "message": "Threshold thr_01j... deleted."
}
  • Deleting a threshold id that doesn't exist still returns 200; the delete is idempotent.

15 endpoints

RAG & Multimodal

Three distinct capabilities that all mount at the router root (no shared path prefix): our own project-scoped document RAG (`/rag/*`), a customer's bring-your-own external RAG backend (`/external-rag/*`), and multimodal image/file memorize + search (`/multimodal/*`). Do not conflate `/rag/*` (our LanceDB-backed store) with `/external-rag/*` (a customer's own RAG service that Personize calls out to).

POST/api/v1.1/rag/ingestSecret keySDK

Ingest documents into a RAG project

Chunks and embeds documents into the org's own RAG project -- a LanceDB-backed store separate from the main memory vector store and from context docs.

Usage: Build a lightweight, project-scoped document index for retrieval-augmented generation without going through the memory/context-doc pipeline.

SDK: client.rag.ingest({ projectId, documents })

Parameters

FieldTypeWhereRequiredDescription
projectIdstringbodyYesRAG project to ingest into. Created automatically on first use.
documentsobject[]bodyYesNon-empty array of `{ id?, text, source?, sourceUrl?, title?, metadata? }`.

Request

{
  "projectId": "proj_docs",
  "documents": [
    { "text": "Our refund policy allows returns within 30 days.", "source": "policy.md", "title": "Refund Policy" }
  ]
}

Response

{ "success": true, "data": { "ingested": 1, "errors": [] } }
  • No separate "create project" call is needed -- the project table is created on first ingest.
  • Embeddings use the org's configured embedding service.
GET/api/v1.1/rag/projectsSecret keySDK

List RAG projects

Lists the org's RAG project IDs.

Usage: Discover what projects already exist before ingesting into a new or existing one.

SDK: client.rag.listProjects()

Response

{ "success": true, "data": { "projects": ["proj_docs"], "count": 1 } }
POST/api/v1.1/rag/deleteSecret keySDK

Delete documents from a RAG project

Removes specific documents from a RAG project by ID.

Usage: Clean up stale or superseded documents from a project's index.

SDK: client.rag.deleteDocuments({ projectId, documentIds })

Parameters

FieldTypeWhereRequiredDescription
projectIdstringbodyYesRAG project the documents belong to.
documentIdsstring[]bodyYesDocument IDs to remove.

Request

{ "projectId": "proj_docs", "documentIds": ["doc_1", "doc_2"] }

Response

{ "success": true, "data": { "deleted": 2 } }
  • Method is POST despite deleting documents -- there is no DELETE-verb route for this operation.
POST/api/v1.1/rag/delete-projectSecret keySDK

Delete an entire RAG project

Drops the entire underlying table for a RAG project, deleting every document in it.

Usage: Tear down a project that's no longer needed.

SDK: client.rag.deleteProject(projectId)

Parameters

FieldTypeWhereRequiredDescription
projectIdstringbodyYesRAG project to delete.

Request

{ "projectId": "proj_docs" }

Response

{ "success": true, "message": "Project proj_docs deleted" }
  • Irreversible.
GET/api/v1.1/external-rag/configSecret key

Get external RAG config (legacy single connection)

Returns the org's legacy single external-RAG connection config with the API key masked, or `{ configured: false }` if none is set.

Usage: Check the currently active external RAG connection before querying or updating it.

Response

{
  "success": true,
  "data": {
    "configured": true,
    "url": "https://partner.example.com/rag",
    "enabled": true,
    "timeout": 10000,
    "maxResults": 10,
    "apiKey": "sk-a...1234"
  }
}
  • Legacy single-connection surface -- prefer the named-connection endpoints (`GET/POST /external-rag/configs`, `DELETE /external-rag/configs/:name`) for new integrations, which support multiple simultaneous external RAG backends.
POST/api/v1.1/external-rag/configSecret keySDK

Set external RAG config (legacy single connection)

Configures the org's legacy single external-RAG connection (stored under the name `default`).

Usage: Point Personize at a customer-hosted RAG backend so `/external-rag/search` and the unified retrieve engine can query it.

SDK: client.rag.configure({ url, apiKey, timeout, maxResults })

Parameters

FieldTypeWhereRequiredDescription
urlstringbodyYesBase URL of the external RAG service (must expose `/search` and `/health`).
apiKeystringbodyYesBearer token sent as `Authorization: Bearer <apiKey>` to the external service.
timeoutnumberbodyNoRequest timeout in ms.
maxResultsnumberbodyNoDefault result cap for this connection.

Request

{ "url": "https://partner.example.com/rag", "apiKey": "sk-partner-key", "timeout": 10000, "maxResults": 10 }

Response

{ "success": true, "data": { "configured": true } }
  • Always sets `enabled: true`.
  • For multiple named connections use `POST /external-rag/configs` instead.
GET/api/v1.1/external-rag/configsSecret keySDK

List named external RAG connections

Lists all of the org's named external RAG connections (Phase 1.x multi-RAG), API keys masked. Includes the legacy single-config row as `name: "default"` if one is set.

Usage: Discover which external RAG connections are configured, for example before filtering the unified retrieve endpoint to specific `connectionNames`.

SDK: client.externalRag.listConfigs()

Response

{
  "success": true,
  "data": {
    "count": 1,
    "configs": [{ "name": "partner-kb", "description": "Partner knowledge base", "url": "https://partner.example.com/rag", "enabled": true, "timeout": 10000, "maxResults": 10, "apiKey": "sk-p...5678" }]
  }
}
POST/api/v1.1/external-rag/configsSecret keySDK

Create or update a named external RAG connection

Creates or updates one named external RAG connection, independent of the legacy single-config row. Companion CRUD for the unified retrieve endpoint's per-source fan-out.

Usage: Register multiple external RAG backends and address them individually via `intent.perSource.external.connectionNames` on the unified retrieve endpoint.

SDK: client.externalRag.saveConfig(config)

Parameters

FieldTypeWhereRequiredDescription
namestringbodyYesConnection name, must match `[a-z0-9][a-z0-9_-]{0,63}` (case-insensitive).
urlstringbodyYesBase URL of the external RAG service.
apiKeystringbodyYesBearer token for the external service.
enabledbooleanbodyNoWhether this connection is active.Default: true
timeoutnumberbodyNoRequest timeout in ms.
maxResultsnumberbodyNoDefault result cap.
descriptionstringbodyNoHuman-readable label for this connection.

Request

{ "name": "partner-kb", "url": "https://partner.example.com/rag", "apiKey": "sk-partner-key", "description": "Partner knowledge base" }

Response

{ "success": true, "data": { "name": "partner-kb", "configured": true } }
  • 400 if `name` doesn't match `[a-z0-9][a-z0-9_-]{0,63}` (case-insensitive).
  • If calling via `client.externalRag.saveConfig()`: the SDK's `ExternalRAGNamedConfig` TypeScript type declares field names (`endpointUrl`, `headers`, `timeoutMs`) that this route does NOT read -- the server only reads `url` / `apiKey` / `timeout` / `maxResults` / `description` as documented above. Construct the request body with those field names rather than relying on the SDK type verbatim, or the call fails with 400 ("url is required").
DELETE/api/v1.1/external-rag/configs/:nameSecret keySDK

Delete a named external RAG connection

Removes one named external RAG connection.

Usage: Decommission an external RAG backend that's no longer in use.

SDK: client.externalRag.deleteConfig(name)

Parameters

FieldTypeWhereRequiredDescription
namestringpathYesConnection name to delete.

Response

{ "success": true, "data": { "name": "partner-kb", "deleted": true } }
POST/api/v1.1/external-rag/testSecret keySDK

Test the external RAG connection

Health-checks the org's configured external RAG endpoint.

Usage: Verify connectivity and latency to a customer's RAG backend before relying on it in production.

SDK: client.rag.test()

Response

{ "success": true, "data": { "healthy": true, "latencyMs": 142 } }
  • 400 if no external RAG config is set.
  • 5-second timeout on the health probe; returns `healthy: false` on any failure or timeout rather than erroring.
POST/api/v1.1/multimodal/memorizeSecret keySDK

Memorize an image or file

Stores an image/document attachment against a record: uploads the original file to S3, generates a multimodal embedding, and (if no `content` is supplied) has an LLM write a short text description for text-search compatibility.

Usage: Make an image or file attachment findable through the same search surface as text memories.

SDK: client.multimodal.memorize({ recordId, type, attachment, mimeType, content })

Parameters

FieldTypeWhereRequiredDescription
recordIdstringbodyYesRecord to associate the attachment with.
attachmentstringbodyYesBase64-encoded file content.
mimeTypestringbodyYesMIME type of the attachment, e.g. `image/png`.
typestringbodyNoAttachment category label.Default: attachment
contentstringbodyNoPre-written text description. If omitted, one is generated by an LLM.

Request

{ "recordId": "rec_abc123", "attachment": "<base64>", "mimeType": "image/png" }

Response

{
  "success": true,
  "data": {
    "stored": true,
    "sourceUrl": "s3://bucket/multimodal/org_abc123/rec_abc123/1735689600000.png",
    "dimensions": 1024,
    "provider": "openai",
    "textDescription": "A screenshot of a pricing table with three tiers."
  }
}
  • Gated behind `MULTIMODAL_ENABLED=true` -- returns 400 if the feature isn't enabled for this deployment.
  • Marked `@internal` in the SDK: not part of the public surface yet -- image similarity search currently falls back to text-only matching against the generated description.
GET/api/v1.1/multimodal/statusSecret keySDK

Check multimodal feature status

Reports whether multimodal embedding is enabled for this deployment, and if so which provider/model/dimension it uses.

Usage: Check this before calling memorize/search -- both return 400 when multimodal is disabled.

SDK: client.multimodal.status()

Response

{
  "success": true,
  "data": { "enabled": true, "provider": "openai", "model": "...", "dimensions": 1024 }
}
  • When disabled: `{ "success": true, "data": { "enabled": false, "message": "Multimodal not enabled. Set MULTIMODAL_ENABLED=true." } } `.