KM-301b · Module 2
The API Layer
6 min read
The API layer is the programmatic interface to the knowledge base — the surface through which integrations, agents, and automated systems interact with knowledge content. Most commercial knowledge base platforms have APIs that range from adequate to poor. When the native API is inadequate for the access patterns required by your stack, you build an abstraction layer on top of the platform API that exposes the capabilities you actually need.
Designing the API layer well at the start prevents a class of technical debt that becomes expensive to fix: downstream systems that have hardcoded assumptions about the knowledge base's data model.
- Retrieval Endpoint Design The retrieval endpoint is the highest-traffic API surface. It needs: full-text search with scoring, semantic search (query by meaning, not just keywords), faceted filter support (filter by content type, topic, audience, date range), and pagination. Return format must include: content body, summary, author, last-reviewed date, source URL, and relevance score. Systems downstream of this endpoint should be able to make retrieval decisions from the response without calling back to the knowledge base for more data.
- Content CRUD Endpoints Create, read, update, and deprecate (not delete). The API should enforce the schema defined in Lesson 3 — attempts to create content missing required fields return a validation error, not a silent default. Update endpoints should support partial updates (PATCH semantics) and must record the updater identity and timestamp for audit trail. Deprecation is a state transition, not a deletion — the content remains accessible via direct ID lookup after deprecation.
- Event Streaming for Change Notification Connected systems — particularly AI agents and search indexes — need to know when content changes. An event stream (or webhook with event types: content.created, content.updated, content.deprecated, taxonomy.changed) allows downstream systems to stay synchronized without polling. Design the event payload to include the full content delta, not just a notification that something changed. Systems that receive an event and must re-query for the content are fragile and add unnecessary load.
- Embedding and Vectorization Support For AI-connected systems, the API should either provide pre-computed embeddings for each content item or expose a webhook that fires whenever content changes so the consumer can re-embed. Pre-computed embeddings from the platform reduce latency and ensure consistency. Consumer-side embedding is more flexible but introduces synchronization complexity. If you control the API layer, implement the pre-computation: it is a better architectural contract.
// Knowledge Base API contract — retrieval endpoint
interface RetrievalRequest {
query: string; // Natural language query
mode: 'full-text' | 'semantic' | 'hybrid'; // Search mode
filters?: {
contentType?: ContentType[]; // article | runbook | decision | faq | reference
topic?: string[]; // From controlled facet list
audience?: string[]; // From controlled facet list
lastReviewedAfter?: string; // ISO date — staleness filter
complexity?: ('Tier 1' | 'Tier 2' | 'Tier 3')[];
};
limit?: number; // Default: 10, max: 50
offset?: number;
}
interface RetrievalResult {
id: string;
contentType: ContentType;
title: string;
summary: string; // Always populated (required schema field)
body: string; // Full content body in markdown
author: {
id: string;
name: string;
};
lastReviewed: string; // ISO date
sourceUrl: string; // Canonical URL for citation
relevanceScore: number; // 0–1, higher is more relevant
facets: {
topic: string[];
audience: string;
complexity: string;
};
freshness: {
status: 'current' | 'review-due' | 'stale';
daysSinceReview: number;
};
}
interface RetrievalResponse {
results: RetrievalResult[];
total: number;
queryId: string; // For feedback loop — link resolution back to query
warning?: string; // E.g., "Top result is 120 days past review date"
}
// Change event payload
interface ContentChangeEvent {
eventType: 'content.created' | 'content.updated' | 'content.deprecated';
contentId: string;
contentType: ContentType;
changedAt: string;
changedBy: string;
delta: {
before?: Partial<RetrievalResult>;
after?: Partial<RetrievalResult>;
};
embeddingInvalidated: boolean; // True when body changed — re-embed downstream
}