spykio-client
v3.3.1
Published
Official client library for the Spykio V3 Graph Query Architecture API.
Readme
Spykio Client
Official client library for the Spykio V3 Graph Query Architecture API.
V3 features a graph-based document network, four parallel retrieval strategies (graph traversal, fuzzy FTS, vector search, entity matching), Reciprocal Rank Fusion scoring, and LLM-driven query decomposition.
Installation
npm install spykio-clientAPI Keys
To use this client, you'll need an API key from Spykio. Visit the Spykio website to sign up and obtain your API key.
Quick Start
const { SpykioClient } = require('spykio-client'); // CommonJS
// or
import { SpykioClient } from 'spykio-client'; // ES Modules
const client = new SpykioClient({
apiKey: 'your-api-key'
});Database
Create a Database
const result = await client.database.create({
dbID: 'my-legal-docs', // Required: human-readable name (max 128 chars)
region: 'EU', // Optional, defaults to 'EU'
skipDomainKnowledge: false // Optional, defaults to false (see below)
});
// Response:
// { success: true, dbID: "my-legal-docs", region: "EU" }List All Databases
const result = await client.database.list();
// Response:
// {
// success: true,
// databases: [
// { dbID: "my-legal-docs", region: "EU", documentCount: 12, skipDomainKnowledge: false, createdAt: "...", updatedAt: "..." }
// ],
// count: 1
// }Check if a Database Exists
const result = await client.database.exists({
dbID: 'my-legal-docs' // Required
});
// Response:
// { success: true, exists: true, dbID: "my-legal-docs" }Delete a Database
const result = await client.database.delete({
dbID: 'my-legal-docs' // Required
});
// Response:
// { success: true, message: "..." }Update Database Settings
Toggle per-database settings such as domain knowledge generation.
const result = await client.database.updateSettings({
dbID: 'my-legal-docs', // Required
skipDomainKnowledge: true // Disable domain knowledge generation & retrieval
});
// Response:
// { success: true, dbID: "my-legal-docs", updated: ["skipDomainKnowledge"] }Get Domain Knowledge
Every database automatically maintains a domain knowledge document — an LLM-generated summary (up to 50,000 characters) that captures the key themes, entities, relationships, and overall scope of all documents in the database. It updates in the background whenever documents are added or removed.
const result = await client.database.domainKnowledge({
dbID: 'my-legal-docs' // Required
});
// Response:
// {
// success: true,
// dbID: "my-legal-docs",
// domainKnowledge: "## Overview\nThis database contains legal contracts and service agreements..."
// }The domain knowledge is also automatically included in every query response as the domainKnowledge field, so you can use it alongside retrieved documents and chunks in your RAG pipeline without making a separate call.
If your use case doesn't benefit from domain knowledge (e.g. high-volume databases where the summary cost isn't worthwhile), you can disable it per-database by setting skipDomainKnowledge: true at creation time or via database.updateSettings(). When disabled, no domain knowledge is generated on ingest and queries return an empty string for the domainKnowledge field.
Recompute Graph
Recompute graph-layer features (edge confidence tiers, community detection, hub metrics) for an existing database. Use this to backfill new graph intelligence features onto databases that were created before v3.3.
Three scopes are available:
| Scope | What it does | Speed |
|-------|-------------|-------|
| communities | Runs community detection + edge counts on existing edges | Instant |
| edges | Invalidates all edges and re-classifies them with confidence tiers | Minutes to hours |
| all | Re-classifies edges, then auto-triggers community detection | Minutes to hours |
// Instant: just compute communities + edge counts from existing edges
const result = await client.database.recompute({
dbID: 'my-legal-docs',
scope: 'communities'
});
// Full rebuild: re-classify all edges with confidence tiers
const result = await client.database.recompute({
dbID: 'my-legal-docs',
scope: 'all'
});
// Response (202):
// {
// success: true,
// dbID: "my-legal-docs",
// scope: "all",
// jobsQueued: 42,
// documentsToRecompute: 42,
// message: "42 edge computation jobs queued. Community detection will run automatically after edges are computed."
// }For the communities scope, the response includes the detection results directly:
// Response (202):
// {
// success: true,
// dbID: "my-legal-docs",
// scope: "communities",
// jobsQueued: 0,
// communities: { communityCount: 5, largestCommunity: 12, documentsUpdated: 42 }
// }Uploading Documents
Upload Text Content (Synchronous)
Text content is ingested synchronously and returns a full result immediately.
const result = await client.files.upload({
dbID: 'my-legal-docs', // Required
content: '# Service Agreement\n\nThis agreement...', // Raw text/markdown
fileName: 'Service_Agreement.md' // Optional
});
// Response (200):
// {
// success: true,
// documentId: "550e8400-e29b-41d4-a716-446655440000",
// fileName: "Service_Agreement.md",
// summary: "A service agreement between...",
// documentType: "contract",
// keywordCount: 12,
// entityCount: 5,
// metrics: { totalTime: 3200 }
// }Upload from URL (Asynchronous)
Pass a URL to ingest a webpage or download a file (PDF, Word, Excel, etc.). The server automatically detects whether the URL points to a downloadable file or a webpage:
- File URL (e.g. a PDF link): The file is downloaded and processed through the standard document pipeline (OCR, markdown conversion, etc.).
- Website URL (e.g. a blog post): The page content is extracted (with full JavaScript rendering) and ingested as text.
URL uploads are always asynchronous and return 202 with a jobId, just like binary file uploads.
// Ingest a PDF from a URL
const result = await client.files.upload({
dbID: 'my-legal-docs',
url: 'https://example.com/reports/annual-report-2025.pdf',
fileName: 'Annual Report 2025' // Optional: override the detected filename
});
// Response (202):
// {
// success: true,
// documentId: "550e8400-...",
// jobId: "7c9e6679-...",
// status: "processing",
// message: "URL queued for processing."
// }
// Ingest a webpage
const webResult = await client.files.upload({
dbID: 'my-legal-docs',
url: 'https://example.com/blog/regulatory-update'
});Poll status the same way as any async upload:
const status = await client.files.status({
jobId: result.jobId,
dbID: 'my-legal-docs'
});Upload a File (Asynchronous)
Binary files (base64-encoded) are queued for background processing and return 202 with a jobId.
const result = await client.files.upload({
dbID: 'my-legal-docs', // Required
base64String: 'JVBERi0xLjQKJ...', // Base64-encoded file
mimeType: 'application/pdf', // MIME type of the file
fileName: 'Contract_2024.pdf' // Optional
});
// Response (202):
// {
// success: true,
// documentId: "550e8400-...",
// jobId: "7c9e6679-...",
// status: "processing",
// message: "File uploaded. Document is being processed in the background."
// }Check Upload Job Status
Poll the status of a background processing job.
const status = await client.files.status({
jobId: '7c9e6679-7425-40de-944b-e07fc1f90ae7', // Required
dbID: 'my-legal-docs' // Required
});
// Response:
// {
// success: true,
// job: {
// job_id: "7c9e6679-...",
// document_id: "550e8400-...",
// job_type: "document_ingestion",
// status: "completed", // pending | processing | completed | failed
// attempts: 1,
// created_at: "...",
// started_at: "...",
// completed_at: "..."
// }
// }Poll Until Complete
const upload = await client.files.upload({
dbID: 'my-legal-docs',
base64String: '...',
mimeType: 'application/pdf',
fileName: 'large-report.pdf'
});
let job;
do {
await new Promise(r => setTimeout(r, 2000));
const res = await client.files.status({ jobId: upload.jobId, dbID: 'my-legal-docs' });
job = res.job;
console.log(`Status: ${job.status}`);
} while (job.status === 'pending' || job.status === 'processing');List Pending Jobs
const result = await client.files.pendingJobs({
dbID: 'my-legal-docs' // Required
});
// Response:
// { success: true, jobs: [...], count: 2 }Documents
List Documents
const result = await client.documents.list({
dbID: 'my-legal-docs', // Required
limit: 20, // Optional, defaults to 100
offset: 0 // Optional, defaults to 0
});
// Response:
// {
// success: true,
// documents: [
// { id: "...", file_name: "...", summary: "...", document_type: "contract", created_at: "...", updated_at: "..." }
// ],
// total: 42
// }Get a Single Document
const result = await client.documents.get({
documentId: '550e8400-e29b-41d4-a716-446655440000', // Required
dbID: 'my-legal-docs' // Required
});
// Response:
// {
// success: true,
// document: {
// id: "...",
// file_name: "Service_Agreement_2024.pdf",
// summary: "A service agreement between...",
// content: "# Service Agreement\n\n...",
// document_type: "contract",
// categories: ["legal", "services"],
// created_at: "...",
// updated_at: "..."
// }
// }Remove a Document
Deletes a document and all associated data.
const result = await client.documents.remove({
documentId: '550e8400-e29b-41d4-a716-446655440000', // Required
dbID: 'my-legal-docs' // Required
});
// Response:
// { success: true, message: "..." }Querying
V3 queries run a multi-strategy pipeline ensuring maximal retrieval quality in the minimum time.
Standard Query (Documents + Chunks)
const result = await client.query.search({
dbID: 'my-legal-docs', // Required
userQuery: 'What are the termination clauses in the contract?', // Required
topN: 10, // Optional: max documents to return (default 10)
region: 'EU', // Optional, defaults to 'EU'
context: 'Looking for early termination conditions in the 2024 SaaS service agreement' // Optional
});
// Response:
// {
// success: true,
// domainKnowledge: "## Overview\nThis database contains legal contracts...",
// documents: [
// {
// id: "550e8400-...",
// fileName: "Service_Agreement_2024.pdf",
// summary: "Service agreement between...",
// documentType: "contract",
// score: 0.087,
// appearanceCount: 4,
// sources: ["graph", "fts", "vector", "entity"]
// }
// ],
// chunks: [
// {
// chunkId: "660e8400-...",
// documentId: "550e8400-...",
// content: "Article 12 - Termination: Either party may terminate...",
// pageNumber: 5,
// chunkIndex: 11,
// sectionHeading: "Article 12 - Termination",
// fileName: "Service_Agreement_2024.pdf",
// score: 0.92,
// metadata: {}
// }
// ],
// metrics: {
// totalTime: 1150,
// timings: { decompose: 480, retrieval: 120, fusion: 2, chunkExtraction: 45 },
// documentCount: 3,
// chunkCount: 8
// }
// }Chunks-Only Query (RAG Mode)
Use chunksOnly: true to return only the most relevant text chunks without document-level metadata. Ideal for feeding into an LLM or RAG pipeline.
const result = await client.query.search({
dbID: 'my-legal-docs',
userQuery: 'What is the notice period for contract termination?',
chunksOnly: true, // Only return chunks
exhaustive: true // Optional: exhaustive reranking fallback (default true)
});
// Response:
// {
// success: true,
// domainKnowledge: "## Overview\nThis database contains legal contracts...",
// chunks: [
// {
// chunkId: "660e8400-...",
// documentId: "550e8400-...",
// content: "Article 12 - Termination: Either party may terminate...",
// pageNumber: 5,
// chunkIndex: 11,
// sectionHeading: "Article 12 - Termination",
// fileName: "Service_Agreement_2024.pdf",
// score: 0.92
// }
// ],
// metrics: {
// totalTime: 680,
// chunkCount: 8
// }
// }Context-Aware Queries
When a query is ambiguous, the optional context parameter lets you disambiguate intent. The context flows through every pipeline stage — query decomposition, vector retrieval, and all reranking passes — so the system understands what you actually mean.
// Without context: retrieves IT hardware cleaning procedures from ISO 27001 docs
const ambiguous = await client.query.search({
dbID: 'iso-27001-docs',
userQuery: 'What cleaning services do you offer?',
chunksOnly: true
});
// With context: the system understands you mean physical building cleaning
const disambiguated = await client.query.search({
dbID: 'iso-27001-docs',
userQuery: 'What cleaning services do you offer?',
context: 'I am asking about a building cleaning company that provides physical cleaning of offices and facilities, not IT equipment cleaning',
chunksOnly: true
});Exhaustive Reranking
By default, exhaustive is true, which enables a deeper search when the initial retrieval doesn't surface strong matches. Set exhaustive: false for faster responses when speed is more important than thoroughness.
const result = await client.query.search({
dbID: 'my-legal-docs',
userQuery: 'liability cap',
exhaustive: false // Skip exhaustive fallback for speed
});Website Indexer
Automatically index an entire website by fetching its sitemap.xml, parsing every URL, and ingesting each page into a dedicated Spykio database. A daily background job re-checks the sitemap and adds any newly discovered pages.
The database is created with skipDomainKnowledge: true and each document's fileName is set to its exact URL for easy lookup.
Create a Website Indexer
const result = await client.websiteIndexer.create({
url: 'https://docs.example.com', // Required: any page on the target site
dbID: 'example-docs', // Optional: defaults to domain-based name
region: 'EU', // Optional, defaults to 'EU'
maxUrls: 5000 // Optional: cap on URLs to ingest (default 10,000)
});
// Response (202):
// {
// success: true,
// indexerId: "a1b2c3d4-...",
// dbID: "example-docs",
// rootUrl: "https://docs.example.com",
// sitemapUrl: "https://docs.example.com/sitemap.xml",
// totalUrls: 347,
// newUrlsQueued: 347,
// skippedExisting: 0,
// databaseAlreadyExisted: false,
// message: "Website indexer created. 347 URLs queued for processing."
// }Once created, the existing v3ProcessBackgroundJobs worker (runs every minute) picks up the queued URLs and ingests them. A separate daily cron re-fetches the sitemap and queues any new pages automatically.
List Website Indexers
const result = await client.websiteIndexer.list();
// Response:
// {
// success: true,
// indexers: [
// {
// indexerId: "a1b2c3d4-...",
// rootUrl: "https://docs.example.com",
// sitemapUrl: "https://docs.example.com/sitemap.xml",
// dbID: "example-docs",
// region: "EU",
// urlCount: 347,
// lastCrawledAt: "...",
// createdAt: "..."
// }
// ],
// count: 1
// }Delete a Website Indexer
Deleting an indexer stops the daily refresh. The associated database and its documents are not deleted.
const result = await client.websiteIndexer.delete({
indexerId: 'a1b2c3d4-...' // Required
});
// Response:
// { success: true, message: "..." }You can query the indexed website the same way you query any Spykio database:
const answer = await client.query.search({
dbID: 'example-docs',
userQuery: 'How do I configure authentication?',
chunksOnly: true
});Browser and Node.js Compatibility
- Browser: Works in modern browsers with Fetch API support.
- Node.js: Works natively in Node.js v18+ (built-in Fetch). For older versions, use a polyfill like
node-fetch.
API Reference
new SpykioClient(options)
| Option | Type | Required | Default | Description |
|----------|--------|----------|-------------------------|-------------------|
| apiKey | string | Yes | — | Your Spykio API key |
| baseUrl| string | No | https://api.spyk.io | API base URL |
client.database.create(options)
| Param | Type | Required | Default | Description |
|-----------------------|---------|----------|---------|----------------------------------------------|
| dbID | string | Yes | — | Database name (max 128 chars) |
| region | string | No | EU | Region |
| skipDomainKnowledge | boolean | No | false | Disable automatic domain knowledge generation|
client.database.list()
No parameters. Returns all databases for the current API key.
client.database.exists(options)
| Param | Type | Required | Description |
|--------|--------|----------|---------------|
| dbID | string | Yes | Database name |
client.database.delete(options)
| Param | Type | Required | Description |
|--------|--------|----------|---------------|
| dbID | string | Yes | Database name |
client.database.domainKnowledge(options)
| Param | Type | Required | Description |
|--------|--------|----------|---------------|
| dbID | string | Yes | Database name |
Returns the auto-generated domain knowledge summary for the database. Also included automatically in every query.search() response.
client.database.updateSettings(options)
| Param | Type | Required | Description |
|-----------------------|---------|----------|------------------------------------------------|
| dbID | string | Yes | Database name |
| skipDomainKnowledge | boolean | No | Toggle automatic domain knowledge on/off |
client.database.recompute(options)
| Param | Type | Required | Default | Description |
|---------|--------|----------|---------|------------------------------------------------------------|
| dbID | string | Yes | — | Database name |
| scope | string | No | all | communities, edges or all |
Returns 202 with job summary. For communities scope, includes detection results directly.
client.files.upload(options)
| Param | Type | Required | Default | Description |
|---------------|--------|----------|-----------------------|--------------------------------------------|
| dbID | string | Yes | — | Target database |
| content | string | No* | — | Raw text/markdown (sync upload, 200) |
| base64String| string | No* | — | Base64-encoded file (async upload, 202) |
| mimeType | string | No* | — | MIME type (required with base64) |
| url | string | No* | — | Website or file URL (async upload, 202) |
| fileName | string | No | Untitled Document | Document file name |
| documentID | string | No | auto-generated | Custom document ID |
| region | string | No | EU | Region |
*Provide one of: content, base64String + mimeType, or url.
client.files.status(options)
| Param | Type | Required | Description |
|--------|--------|----------|-------------------------|
| jobId| string | Yes | Job ID from async upload|
| dbID | string | Yes | Database name |
client.files.pendingJobs(options)
| Param | Type | Required | Description |
|--------|--------|----------|---------------|
| dbID | string | Yes | Database name |
client.documents.list(options)
| Param | Type | Required | Default | Description |
|----------|---------|----------|---------|----------------------|
| dbID | string | Yes | — | Database name |
| limit | integer | No | 100 | Max results |
| offset | integer | No | 0 | Pagination offset |
client.documents.get(options)
| Param | Type | Required | Description |
|-------------|--------|----------|---------------|
| documentId| string | Yes | Document UUID |
| dbID | string | Yes | Database name |
client.documents.remove(options)
| Param | Type | Required | Description |
|-------------|--------|----------|---------------|
| documentId| string | Yes | Document UUID |
| dbID | string | Yes | Database name |
client.query.search(options)
| Param | Type | Required | Default | Description |
|-------------|---------|----------|---------|---------------------------------------------|
| dbID | string | Yes | — | Target database |
| userQuery | string | Yes | — | Natural language query |
| region | string | No | EU | Region |
| topN | integer | No | 10 | Max documents to return |
| chunksOnly| boolean | No | false | When true, returns only chunks (RAG mode) |
| exhaustive| boolean | No | true | When true, falls back to exhaustive LLM reranking if initial passes filter all chunks |
| context | string | No | '' | Additional context to disambiguate the query (e.g. domain, intent, constraints) |
client.websiteIndexer.create(options)
| Param | Type | Required | Default | Description |
|-----------|---------|----------|----------|-------------------------------------------------------|
| url | string | Yes | — | Any URL on the target site (root is extracted automatically) |
| dbID | string | No | auto | Database name (auto-generated from domain if omitted) |
| region | string | No | EU | Region |
| maxUrls | integer | No | 10000 | Maximum number of sitemap URLs to process |
client.websiteIndexer.list()
No parameters. Returns all website indexers for the current API key.
client.websiteIndexer.delete(options)
| Param | Type | Required | Description |
|-------------|--------|----------|----------------------------------|
| indexerId | string | Yes | Indexer ID returned by create |
Migration from V1 (< 2.0.0)
The V1 API and all methods documented below are deprecated and will be removed in a future release. Please migrate to V3 (documented above).
Breaking Changes in 2.0.0
| V1 (deprecated) | V3 (current) | Notes |
|------------------------------------|-------------------------------------|----------------------------------------------|
| index parameter | dbID parameter | Renamed everywhere |
| dbName parameter | dbID parameter | Renamed everywhere |
| query.search({ index, ... }) | query.search({ dbID, ... }) | Completely new params (see below) |
| accurateMatch, getRelevantInfo | Removed | V3 always uses multi-strategy retrieval |
| experimentalMode, maxResults | Removed | Use topN and chunksOnly instead |
| documents.extract() | Removed | Use query.search({ chunksOnly: true }) |
| files.upload({ async: true }) | Automatic | Binary uploads are always async (202) |
| files.status({ jobId }) | files.status({ jobId, dbID }) | dbID is now required |
| files.pendingJobs({ index }) | files.pendingJobs({ dbID }) | No region param needed |
| files.remove() | documents.remove() | Moved to documents namespace |
| database.create({ dbName }) | database.create({ dbID }) | Param renamed |
| database.exists({ dbName }) | database.exists({ dbID }) | Param renamed |
| database.delete({ index }) | database.delete({ dbID }) | Param renamed |
| — | database.list() | New in V3 |
| — | documents.get() | New in V3 |
| — | database.domainKnowledge() | New in V3 — auto-generated knowledge summary |
| — | database.updateSettings() | New in V3.2 — toggle skipDomainKnowledge |
| — | files.upload({ url }) | New in V3.2 — ingest from website or file URL|
| — | websiteIndexer.create() | New in V3.3 — index entire website via sitemap |
| — | websiteIndexer.list() | New in V3.3 — list website indexers |
| — | websiteIndexer.delete() | New in V3.3 — remove a website indexer |
| — | database.recompute() | New in V3.3 — recompute graph features (confidence, communities, hub metrics) |
Deprecated V1 Documentation
Searching (V1)
const result = await client.query.search({
index: 'your-index-name',
userQuery: 'What hotel did I stay at in Warsaw?',
accurateMatch: false,
getRelevantInfo: false
});Extracting Information (V1)
const result = await client.documents.extract({
index: 'your-index-name',
documentId: 'abc123-example-id',
userQuery: 'What is the hotel address?'
});Listing Documents (V1)
const result = await client.documents.list({
index: 'your-index-name',
region: 'EU',
limit: 20,
offset: 0
});Creating a Database (V1)
const result = await client.database.create({
dbName: 'your-database-name'
});Checking Database Existence (V1)
const result = await client.database.exists({
dbName: 'your-database-name',
region: 'EU'
});Deleting a Database (V1)
const result = await client.database.delete({
index: 'your-database-name',
region: 'EU'
});Uploading Files (V1)
const result = await client.files.upload({
index: 'your-index-name',
mimeType: 'application/pdf',
base64String: 'base64EncodedString',
async: true,
region: 'EU'
});Removing Documents (V1)
const result = await client.files.remove({
index: 'your-index-name',
documentId: 'abc123-example-id',
region: 'EU'
});