@schift-io/sdk
v4.0.0
Published
Workspace SDK for bucket search, documents, jobs, and Structify
Maintainers
Readme
@schift-io/sdk
TypeScript SDK 4.0.0 for bucket search, document upload, jobs, and Structify.
Install
npm install @schift-io/sdkInstall from the private monorepo before npm release
The SDK package is nested under clients/sdk/ts, so an npm Git URL would
install the repository root instead of this package. Clone the private
monorepo, build the SDK, and install that directory:
git clone --depth 1 [email protected]:schift-io/schift.git .vendor/schift
npm --prefix .vendor/schift/clients/sdk/ts install
npm --prefix .vendor/schift/clients/sdk/ts run build
npm install ./.vendor/schift/clients/sdk/tsPin the clone to the reviewed commit in CI rather than tracking a moving branch. This is a pre-release installation path; no npm release or tag is required.
Current scope
The current repository release lane keeps the public SDK focused on bucket
search and ingest, documents, jobs, and Structify document extraction. Agent Hub, RAG chat, LLM completions, the agent framework, Warehouse Data,
gateway vectors, usage reads, provider-key management, migration, benchmark-suite and
drift-monitor clients are removed as of 1.0.0. client.agents (Managed Agents) and
the APM versioning client are removed as of 3.0.0. Embeddings, the model catalog, collections,
rerank, aggregate, routing, artifacts, bucket context and bucket graph clients are removed as of
4.0.0; use bucket search / retrieve instead (see CHANGELOG).
Quick Start
import { WorkspaceClient } from "@schift-io/sdk";
const client = new WorkspaceClient({
apiKey: "sch_your_api_key",
baseUrl: process.env.SCHIFT_API_URL!,
});
// Create or reuse a bucket, upload a document, then search it.
// All bucket methods accept a name or ID — no need to track UUIDs.
await client.createBucket({ name: "company-docs" });
const file = new File([await readFile("manual.pdf")], "manual.pdf", {
type: "application/pdf",
});
await client.db.upload("company-docs", { files: [file] });
const jobs = await client.listJobs({ bucket: "company-docs", limit: 5 });
const search = await client.bucketSearch("company-docs", {
query: "refund policy",
topK: 5,
});
console.log(jobs[0]?.status ?? "queued");
console.log(search.context, search.citations);Use search({ bucket: ... }) for an answer-ready context block with citations, retrieve(bucketId, { query }) for raw scored chunks.
API Key
Create or copy your API key in Console → API keys (/app/console/keys).
Configure the hosted API origin with baseUrl, SCHIFT_API_URL, or
SCHIFT_BASE_URL. The SDK does not assume a production origin when none is
configured.
const client = new WorkspaceClient({
apiKey: process.env.SCHIFT_API_KEY!,
baseUrl: process.env.SCHIFT_API_URL!,
});Local Auth Readiness
/v1/auth/me validates a user JWT, not a sch_* API key. Smoke tests should
use fixed accounts from .env/.env.local; do not generate timestamped signup
emails against production.
import { WorkspaceClient } from "@schift-io/sdk";
const auth = WorkspaceClient.auth({ baseUrl: "http://127.0.0.1:8011" });
await auth.health();
const login = await auth.login({
email: process.env.SCHIFT_SMOKE_QA_EMAIL!,
password: process.env.SCHIFT_SMOKE_QA_PASSWORD!,
});
const me = await auth.me(login.token!);
console.log(me.user.email, me.orgs[0]?.name);With a local API already running:
SCHIFT_API_URL=http://127.0.0.1:8011 \
[email protected] \
SCHIFT_SMOKE_QA_PASSWORD=... \
npm run smoke:authFeatures
Search (answer-ready)
const response = await client.search({
query: "How does projection work?",
bucket: "my-docs",
topK: 10,
});
// response: { status, operational_status, bucket_id, query,
// context: string, citations: [{ index, title?, page?, ... }],
// warnings: [{ code, message, severity }] }Retrieve (raw chunks)
const retrieved = await client.retrieve("my-docs-bucket-id", {
query: "refund policy",
topK: 8,
options: { rerank: { enabled: true } },
});
// retrieved.results: Array<{ chunk_id, text, score, document_id?, metadata?, ... }>Provider keys
The 0.16.0 core SDK does not manage provider keys. Configure provider
connections in the supported Schift console surface or in the application
runtime that owns the provider credentials; do not use a client.providers
helper from this package.
Agent SDK Compatibility
The SDK sits underneath the agent framework. The integration point is always the same:
- let the agent call a workspace search tool
- run retrieval against workspace buckets
- return grounded chunks back to the model
That means you can keep your preferred agent SDK and swap only the retrieval layer.
Google Gen AI SDK
import { GoogleGenAI } from "@google/genai";
import { WorkspaceClient } from "@schift-io/sdk";
const ai = new GoogleGenAI({ apiKey: process.env.GEMINI_API_KEY });
const client = new WorkspaceClient({
apiKey: process.env.SCHIFT_API_KEY!,
baseUrl: process.env.SCHIFT_API_URL!,
});
const firstTurn = await ai.models.generateContent({
model: "gemini-2.5-flash",
contents: "What changed in the latest billing policy?",
config: {
tools: client.tools.googleGenAI(),
},
});
const functionCall = firstTurn.functionCalls?.[0];
if (functionCall) {
const functionResponsePart = await client.tools.googleFunctionResponse({
name: functionCall.name,
args: functionCall.args ?? {},
});
const secondTurn = await ai.models.generateContent({
model: "gemini-2.5-flash",
contents: [
{ role: "user", parts: [{ text: "What changed in the latest billing policy?" }] },
{ role: "model", parts: [{ functionCall }] },
{ role: "user", parts: [functionResponsePart] },
],
});
console.log(secondTurn.text);
}Vercel AI SDK
import { generateText } from "ai";
import { openai } from "@ai-sdk/openai";
import { WorkspaceClient } from "@schift-io/sdk";
const client = new WorkspaceClient({
apiKey: process.env.SCHIFT_API_KEY!,
baseUrl: process.env.SCHIFT_API_URL!,
});
const result = await generateText({
model: openai("gpt-4o-mini"),
prompt: "What changed in the latest billing policy?",
tools: client.tools.vercelAI(),
maxSteps: 5,
});
console.log(result.text);Mastra
If you are using Mastra, wrap client.search() in a Mastra tool and keep the rest of the agent stack unchanged.
import { Agent } from "@mastra/core/agent";
import { createTool } from "@mastra/core/tools";
import { openai } from "@ai-sdk/openai";
import { z } from "zod";
import { WorkspaceClient } from "@schift-io/sdk";
const client = new WorkspaceClient({
apiKey: process.env.SCHIFT_API_KEY!,
baseUrl: process.env.SCHIFT_API_URL!,
});
const workspaceSearchTool = createTool({
id: "workspace-search",
description: "Retrieve context from a workspace bucket.",
inputSchema: z.object({
bucket: z.string(),
query: z.string(),
topK: z.number().int().min(1).max(10).default(5),
}),
execute: async ({ context }) => ({
search: await client.search({
bucket: context.bucket,
query: context.query,
topK: context.topK,
}),
}),
});
const agent = new Agent({
name: "docs-agent",
instructions: "Use workspace-search before answering document questions.",
model: openai("gpt-4o-mini"),
tools: { workspaceSearchTool },
});Examples:
Buckets
// List all buckets
const buckets = await client.listBuckets();
await client.db.upload("company-docs", {
files: [file],
});
const collections = await client.listBucketCollections("company-docs");Bucket search is permission-scoped: the server searches only the child collections the caller can access and merges the ranked results.
Document Metadata And Connectors
Uploading a document or syncing it in through a connector (Notion, Gmail, Obsidian, …) attaches metadata you can filter on at search time.
Reserved keys — populated structurally (connector API field mapping or Markdown frontmatter parsing, never LLM-inferred). Availability depends on what the source provides.
| Key | Meaning | Always set? |
|---|---|---|
| source_kind | Kind of source (notion_page, email, markdown_note, upload, …) | yes |
| source_created_at | Original creation time (ISO 8601) | if source provides it |
| source_modified_at | Original last-modified time (ISO 8601) | if source provides it |
| source_author | Author | if source provides it |
| source_uri | Deep link back to the original source | if source provides it |
| source_filename | File name | uploads always; connectors if provided |
| source_tags | Tags/labels from the source (JSON-encoded array string) | if source provides it |
| ingested_by | Who/what ingested it (upload or connector:<provider>) | yes |
| ingested_at | When Schift received the document | yes |
| cclg_source | Internal lineage identifier | yes |
Avoid naming custom metadata keys the same as these — the system value takes precedence.
For Markdown uploads (.md/.markdown), pass parseFrontmatter: true to
promote YAML frontmatter (created/date/author/tags) into the
reserved keys above:
await client.db.upload("company-docs", {
files: [file],
parseFrontmatter: true,
});Custom metadata is always explicit — Schift does not infer business metadata from document content:
await client.db.upload("company-docs", {
files: [file],
metadata: { department: "finance", priority: "high" },
});Custom metadata is set per document and inherited by every chunk derived from it. Values must be strings, numbers, or booleans (coerced to strings server-side).
Filtering — reserved and custom keys are filtered the same way, with
flat, top-level keys (not a nested { metadata: {...} } object):
const resp = await client.search({
query: "revenue guidance",
bucket: "company-docs",
filters: {
source_kind: "email",
department: "finance",
},
});There is no contains operator today — use like ({ like: "%urgent%" })
for substring matches, including against JSON-encoded fields like
source_tags.
Configuration
const client = new WorkspaceClient({
apiKey: "sch_...", // required
baseUrl: process.env.SCHIFT_API_URL, // required unless SCHIFT_API_URL/SCHIFT_BASE_URL is set
timeout: 60_000, // default, in milliseconds
});Error Handling
import {
WorkspaceClient,
AuthError,
QuotaError,
PlatformError,
} from "@schift-io/sdk";
const client = new WorkspaceClient({
apiKey: process.env.SCHIFT_API_KEY!,
baseUrl: process.env.SCHIFT_API_URL!,
});
try {
await client.search({ bucket: "my-docs", query: "test" });
} catch (err) {
if (err instanceof AuthError) {
// 401: Invalid or expired API key
} else if (err instanceof QuotaError) {
// 402: Insufficient credits
} else if (err instanceof PlatformError) {
// Other API errors (403, 422, 429, 500, 502)
console.error(err.message, err.status);
}
}Releases
Published from the schift-io/schift monorepo to npm via
.github/workflows/sdk-publish-ts.yml. Cut a release with:
# 1. Bump version
$EDITOR clients/sdk/ts/package.json # version: "0.X.Y"
git add clients/sdk/ts/package.json && git commit -m "chore(sdk-ts): bump 0.X.Y"
git push origin main
# 2. Create the release (tag pattern: npm-v*)
gh release create npm-v0.X.Y \
--title "@schift-io/sdk v0.X.Y" \
--notes "..."The workflow verifies the tag matches package.json, runs tsc, npm test,
npm run build, then npm publish --access public.
The public mirror at
schift-io/schift-tsis sync-only (no workflows there). All publish automation lives in the monorepo.
License
MIT
이 SDK는 직접 설정한 Schift API와 그 OpenAI 호환 프록시에 Schift-Version: 2026-10-01을 보내며, 독립 OpenAI 호환 헬퍼는 알려진 Schift 주소 또는 PLATFORM_*·SCHIFT_* API URL 설정과 정확히 같은 origin(스킴·호스트·포트)에만 헤더를 추가한다.
