Changelog
Stay up to date with all the latest features, improvements, and bug fixes.
Two Flux API refinements: capping long text in list responses, and an MCP error hint that finally points agents at the right fix.
Flux API
truncate_texton list and search. Addtruncate_text=<n>to the query string on List Resources or Search to cap everytextfield toncharacters — handy when a page of long documents is far larger than the caller needs. Opt-in and off by default: without it, responses are byte-for-byte unchanged. Onlytext-typed fields are affected (identifiers, keys, and_sysare never touched;raw=trueignores it), and every shortened field is flagged in the record's_sys.truncatedarray (field path, locale, original length) so a fragment is never mistaken for a whole document. Result count, ordering, relevance, and cursors are computed on the full data and are unchanged.
MCP
access_deniednow returns a cause-specific hint. Aget_recordagainst a collection connected withoutget_oneused to returnaccess_deniedwith the genericunknown_resourcehint ("reuse the exactresource_id") — sending the agent to re-confirm aresource_idthat was already correct. The hint now names the actual remedy: usequery_records/search_recordswhen the method isn't enabled for the collection, or a different key/role when the key lacks access.error_codeandmessageare unchanged, and theunknown_resourcehint is unchanged.
A strict-reference collection can now be read across its parents — a portal front page can list every listing, not the listings of one agency — without loosening the scoped route or changing how anything is written.
Flux API
- New read addresses per collection. Alongside the scoped route, a strict-reference collection can publish additional read-only addresses that drop one or more ancestor keys. Two forms: fully-flat / unscoped (
/realty/accounts/listings/photos— every resource across all parents) and partially-flat (/realty/accounts/{account_key}/listings/photos— everything under one higher ancestor). They serve List Resources, Search, Get Resource and Schema; there is no create/update/delete on a flat address. Strictly additive — the scoped route is unchanged. See Cross-Parent Reads. - On a public API, a fully-flat address is an anonymous, cross-tenant read of the whole collection — a deliberate exposure, opt-in only. On a key-authenticated API the address changes the scope, never the permission check.
/_routerlists the cross-parent addresses actually being served.
Management API
- New fields on the collection connection:
unscoped_levels(which levels to publish) plus theunscoped_ancestorschain it publishes against, and a read-onlyflat_routesdescribing every publishable address and whether it is enabled. See the Collection Connection Object.
Dashboard
- Enabled cross-parent addresses are listed among the collection's endpoints; an Add a cross-parent address control publishes one, asking for confirmation before widening exposure. A single click removes one.
Public Flux APIs can now be called directly from browser JavaScript — a static page gets typed reads and hybrid search with no server of its own.
Management API
- New field on the API object:
cors_origins(array of origins, default[]= off). Set explicit origins or["*"]for a public API — anonymous reads are already open server-side, so wildcard CORS adds no new exposure. See APIs.
Flux API
- With
cors_originsconfigured, cross-originGETreads andPOSTsearch work from browsers. PreflightOPTIONSrequests are answered immediately and are never billed. Error responses (402, rate limiting) carry CORS headers, so client code receives the machine-readable body instead of an opaque error.Retry-Afteris exposed to JavaScript. - Browser CORS covers public, key-free traffic only:
Authorizationis not an allowed browser header, writes still require an authenticated key, and/{api_prefix}/_mcpis not covered — MCP clients are not browsers.
The write capability added with the MCP write tools is now available over plain REST — same pipeline, same permissions, same billing, two transports.
Flux API
- New endpoints:
POST /{api_prefix}/{collection_path}/(create + publish) andPUT .../{resource_key}/(full-document replace → new published revision). NoPATCH, no delete. - Same double opt-in as MCP: the key's role needs
create/updategrants, the collection connection must allow the method. Writes always require an authenticated key — public APIs accept anonymous reads only. /_routernow advertises write routes to keys that hold the grants.- Billing: one weighted Write per call, never a retrieval; failed calls free.
Plan quotas now live where the resources live. Per organization: projects and members. Per project: environments (production included; protection settings never affect the count). Per environment: collections, components, Flux APIs, locales, API keys, and custom roles — each environment gets the full allowance, so adding an environment never eats into another one's capacity. Plan numbers are unchanged; only the scopes are clarified. See Limits.
Management API
GET /organizations/{org_key}/usage/now reports only org-scoped structural entities (projects,resources,users) alongside storage and billing usage.environments,collections, andcomponentsleft the response — they are enforced per project / per environment at create time and no longer have an org-wide meaning.- Exceeding a scoped quota returns the same
403 plan_limit_exceededwith the affecteddetail.entity.
The per-API MCP server gains two opt-in write tools — create_record and update_record. An agent connected over MCP can now persist knowledge through the same connector it reads from: the natural fit for agent-memory setups, where the agent reads the whole knowledge base and writes what it learns into a dedicated collection.
Flux API
- Two new tools on
POST /{api_prefix}/_mcp:create_record(create + publish a record) andupdate_record(replace a record with a new published revision). Every write runs the full publish pipeline — schema validation, revisions, vectorization — exactly like a Management API write. There is deliberately no delete tool. See Write tools. - Double opt-in permissions. Write access requires both the key's role (
create/updategrants on the API) and the collection connection (create/updatein itsallowed_methods, off by default). Keys without write grants don't even see the write tools intools/list. The typical setup — read ten collections, write into one — is one API with writes enabled on a single collection. discover_resourcesnow annotates each resource withwritable(subset ofcreate,update), so agents know where they may write without probing.- New tool-level errors:
collection_not_writable,content_validation_failed(carries the standard content validation payload withjson_pathitems),external_id_conflict.
Billing
- A write-tool
tools/callmeters as a weighted Write —max(1, ceil(embedded_tokens / 500)), same formula as REST writes — and is never additionally counted as a Retrieval. The result reports the charge inwrite_units. Failed calls remain free.
Dashboard
- Collection connections in the Flux API editor gain Create / Update toggles (off by default).
- Flux Access roles gain create / update grants for the MCP write tools.
Documentation
- New guide: Multi-Tenant Agent Memory — per-user agent memory with enforced tenant scoping (strict reference), scoped MCP writes, and one-call tenant deletion with vector cleanup.
Every Flux API prefix now exposes a built-in Model Context Protocol server alongside its REST endpoints, and two new governance toggles let you control which APIs are discoverable by AI agents. Together they make Flux APIs directly usable by Claude, ChatGPT, the OpenAI Responses API, Cursor, and any MCP-compatible client — without writing a custom tool server or maintaining a second access-control surface.
Flux API
- New endpoint:
GET | POST | DELETE /{api_prefix}/_mcp— per-API MCP server over Streamable HTTP (2025-03-26). Publishes a fixed five-tool catalog (discover_resources,describe_resource,get_record,query_records,search_records) scoped to the connected collections of the prefix. Thesearch_recordstool exposes hybrid retrieval (vector + full-text + structured filters) as a single call. See MCP Server reference. - Same auth and permissions as the REST surface:
is_auth_requiredcontrols anonymous access, per-collectionallowed_methodscontrols which tools are available per collection, Flux API key roles apply identically.
Management API
- New fields on the API object (returned by
GET, accepted byPOSTandPATCH):mcp_enabled(boolean, defaulttrue) — whenfalse, the/{api_prefix}/_mcpendpoint returns404router_introspection_enabled(boolean, defaulttrue) — whenfalse, the/{api_prefix}/_routerendpoint returns404
- Both fields are independent policy switches. The data routes (
/{collection},/{collection}/{key},/{collection}/_search) are never affected. - Both fields use strict JSON-boolean validation — strings like
"true", integers, andnullare rejected with422. PATCH /v1/{env}/api/{api}/supports single-field updates:{"mcp_enabled": false}works without re-sending the entire API object.- Both fields are captured in the API delete-event audit snapshot.
Documentation
- New concept page: Agent-Native Flux APIs — what "agent-native" means here, the three introspection endpoints, the differentiators vs. hand-written MCP servers, and a per-prefix governance model.
- New guides: Connect Claude and Connect ChatGPT and OpenAI.
- LLM Integrations now lists "MCP Server Connection" as a fourth integration pattern alongside Direct API, LangChain Retriever, and Agent Tool.
Rollout note
Router items written before this release lack the agent_features block in DynamoDB; the Flux API treats a missing block as defaults-on. Items refresh automatically on the next router-build trigger (any API or collection edit). No data migration is required.
The Flux _search endpoint now returns a metadata object in every response, showing the effective parameters that were used — including server-side defaults.
"metadata": {
"search_mode": "vector",
"limit": 100,
"vector_search_enabled": true,
"top_k": 10,
"similarity_threshold": 0.4
}
The metadata shape varies by search mode. See the Search API reference for the full list of fields per mode.
Fixed a bug where vector search modes did not pass limit to the Flux API, causing the top_k parameter to have no effect on the number of returned documents.
The package now conforms to LangChain integration standards and passes the official RetrieversIntegrationTests suite.
pip install langchain-foxnose==0.3.1
Both official SDKs now have typed models and convenience methods for all Flux vector search modes: vector_search, vector_field_search, hybrid_search, and boosted_search.
Python SDK 0.5.0
- New
flux.modelsmodule:SearchMode,VectorSearch,VectorFieldSearch,VectorBoostConfig,HybridConfig,SearchRequestwith cross-field validation - Convenience methods on
FluxClient/AsyncFluxClientwith**extra_bodypass-through forwhere,sort, etc.
results = client.hybrid_search(
"articles",
query="ML applications",
find_text={"query": "machine learning"},
vector_weight=0.7,
text_weight=0.3,
)
TypeScript SDK 0.3.0
- New types:
SearchMode,VectorSearch,VectorFieldSearch,VectorBoostConfig,HybridConfig,SearchRequestwithbuildSearchBody()runtime validation - Convenience methods on
FluxClientwith extra parameter forwarding via rest spread
const results = await client.hybridSearch('articles', {
query: 'ML applications',
findText: { query: 'machine learning' },
vectorWeight: 0.7,
textWeight: 0.3,
});
FoxNose now supports a full workflow for custom embeddings: you can define vector fields in Management API, store model-generated vectors directly in resources, and search them in Flux with vector_field_search.
This unlocks semantic experiences beyond classic text search, including voice/speaker matching, image similarity, recommendation and ranking scenarios, while keeping search behavior explicit and predictable in vector and vector_boosted modes.
For custom vector search, the distance metric is cosine only.
Management API
- New field type:
vector - Supported dimensions:
256,384,768,1024,1536
Flux API
- New search payload:
vector_field_search - Supported parameters:
field,query_vector,top_k,similarity_threshold vector_field_searchis supported withsearch_mode="vector"andsearch_mode="vector_boosted"vector_field_searchis not supported withsearch_mode="text"orsearch_mode="hybrid"vector_field_searchandvector_searchare mutually exclusive- Distance metric for vector field search: cosine only
Support for Flux API introspection and route-level descriptions on API collection connections.
New: Flux introspection methods
FluxClient.getRouter()— callsGET /{api_prefix}/_routerFluxClient.getSchema(folderPath)— callsGET /{api_prefix}/{collection_path}/_schema
const router = await fluxClient.getRouter();
const schema = await fluxClient.getSchema('content/articles');
Updated: API collection connection methods
ManagementClient.addApiFolder()andupdateApiFolder()now accept optional route descriptions:descriptionGetOnedescriptionGetManydescriptionSearchdescriptionSchema
const connection = await managementClient.addApiFolder(apiKey, folderKey, {
allowedMethods: ['get_many', 'get_one'],
descriptionGetOne: 'Get one article by key',
descriptionGetMany: 'List published articles',
descriptionSearch: 'Search published articles',
descriptionSchema: 'Read article schema',
});
Updated model
APIFolderSummarynow includes alldescription_*fields from Management API responses
Support for Flux API introspection and route-level descriptions on API collection connections.
New: Flux introspection methods
FluxClient.get_router()/AsyncFluxClient.get_router()— callsGET /{api_prefix}/_routerFluxClient.get_schema(folder_path)/AsyncFluxClient.get_schema(folder_path)— callsGET /{api_prefix}/{collection_path}/_schema
router = flux_client.get_router()
schema = flux_client.get_schema("content/articles")
Updated: API collection connection methods
ManagementClient.add_api_folder()andupdate_api_folder()now accept optional route descriptions:description_get_onedescription_get_manydescription_searchdescription_schema
- Same signature updates are available on
AsyncManagementClient
connection = management_client.add_api_folder(
api_key,
folder_key,
allowed_methods=["get_many", "get_one"],
description_get_one="Get one article by key",
description_get_many="List published articles",
description_search="Search published articles",
description_schema="Read article schema",
)
Updated model
APIFolderSummarynow includes alldescription_*fields from Management API responses
New introspection capabilities for Flux APIs to improve runtime discovery for users, MCP servers, and LLM agents.
Flux API
- New endpoint:
GET /{api_prefix}/_router— returns a flat route catalog with method, path, action, scope, path params, query params, request body contract, and response shape - New endpoint:
GET /{api_prefix}/{collection_path}/_schema— returns live JSON Schema and route metadata for the target collection path /_schemanow includes searchable metadata:searchable_fields— fields available for filtering/searchnon_searchable_fields— fields present in schema but not indexed for search
Management API
- Collection-to-API connection supports per-action route descriptions:
description_get_manydescription_get_onedescription_searchdescription_schema
- These descriptions are used by Flux
/_routerfor each action route
Initial release of the official LangChain.js integration for FoxNose.
Features
- FoxNoseRetriever — LangChain
BaseRetrieverbacked by FoxNose Flux search with text, vector, hybrid, and vector-boosted modes - FoxNoseLoader —
BaseDocumentLoaderwith cursor-based pagination andloadLazy()async generator for memory-efficient bulk loading - createFoxNoseTool — factory that wraps retrieval as a
DynamicStructuredToolfor LLM agents - Flexible content mapping: single field, multiple fields, or custom mapper
- Metadata control: whitelist, blacklist, system metadata toggle
- Structured filtering via
whereparameter - Dual output: ESM and CommonJS with full TypeScript declarations
Installation
npm install @foxnose/langchain @foxnose/sdk @langchain/core
Quick Start
import { FluxClient, SimpleKeyAuth } from '@foxnose/sdk';
import { FoxNoseRetriever } from '@foxnose/langchain';
const client = new FluxClient({
baseUrl: 'https://your-env.fxns.io',
apiPrefix: 'content',
auth: new SimpleKeyAuth('YOUR_PUBLIC_KEY', 'YOUR_SECRET_KEY'),
});
const retriever = new FoxNoseRetriever({
client,
folderPath: 'knowledge-base',
pageContentField: 'body',
searchMode: 'hybrid',
});
const docs = await retriever.invoke('how to build AI applications');
Links
Initial release of the official FoxNose TypeScript SDK.
Features
- ManagementClient — full coverage of Management API v1: collections, components, resources, revisions, roles, API keys, environments, projects, and more
- FluxClient — content delivery: list resources, get resource, search with text/vector/hybrid modes
- Four auth strategies — Anonymous, JWT, Simple key, and Secure (ECDSA P-256)
- Automatic retries — exponential backoff with jitter and Retry-After support
- Batch upsert — concurrent resource upserts with configurable
maxConcurrencyand progress callback - Zero dependencies — built on native
fetch(Node 18+) - Dual output — ESM and CommonJS with full TypeScript declarations
Installation
npm install @foxnose/sdk
Quick Start
import { FluxClient, SimpleKeyAuth } from '@foxnose/sdk';
const client = new FluxClient({
baseUrl: 'https://your-env.fxns.io',
apiPrefix: 'content',
auth: new SimpleKeyAuth('YOUR_PUBLIC_KEY', 'YOUR_SECRET_KEY'),
});
const resources = await client.listResources('articles');
const results = await client.search('articles', {
search_mode: 'hybrid',
vector_search: { query: 'how to build AI applications', top_k: 5 },
});
client.close();
Links
New external_id field on resources enables idempotent syncing from external systems.
Upsert by external_id
- New endpoint:
PUT /v1/:env/collections/:collection/resources/?external_id=<value> - Creates a new resource if the
external_iddoesn't exist in the collection, or creates a new revision if it does - Returns
201 Created,200 OK, or202 Accepted(async mode) - Supports all standard resource options:
name,mode,resource_owner,validate_data,component
external_id on resources
- Optional identifier — allowed characters:
a-z A-Z 0-9 - _ / ., max 255 characters - Can be set during
POSTcreation orPUTupsert POSTreturns409 external_id_conflictif the ID is already taken in the collection- Returned in the resource object as
external_id(nullwhen not set)
Improved error handling
- Non-GET requests (POST, PUT, PATCH, DELETE) to URLs without a trailing slash now return
422 trailing_slash_requiredinstead of500 - GET requests without a trailing slash continue to redirect via
301
Endpoints
PUT /v1/:env/collections/:collection/resources/?external_id=<value>— Upsert ResourcePOST /v1/:env/collections/:collection/resources/— Create Resource (now accepts optionalexternal_id)
Full support for external_id and upsert operations, including concurrent batch upserts.
New: upsert_resource()
- Creates a resource if the
external_iddoesn't exist, or creates a new revision if it does - Uses
PUT /collections/:collection/resources/?external_id=<value>under the hood
collection = client.get_collection_by_path("content/blog-posts")
resource = client.upsert_resource(
collection,
{"title": "Hello World", "body": "..."},
external_id="post-42",
)
New: batch_upsert_resources()
- Upserts multiple resources in parallel with configurable concurrency
- Collects successes and failures into a
BatchUpsertResult fail_fast=Truestops on first error;fail_fast=False(default) processes all items- Optional
on_progresscallback:(completed_count, total_count)
from foxnose_sdk import BatchUpsertItem
collection = client.get_collection_by_path("content/articles")
items = [
BatchUpsertItem(external_id=f"article-{i}", payload={"title": f"Article {i}"})
for i in range(100)
]
result = client.batch_upsert_resources(
collection,
items,
max_concurrency=10,
on_progress=lambda done, total: print(f"{done}/{total}"),
)
print(f"OK: {result.success_count}, Failed: {result.failure_count}")
Updated: create_resource()
- New optional
external_idparameter to assign an external ID during creation
collection = client.get_collection_by_path("content/blog-posts")
resource = client.create_resource(
collection,
{"title": "New Post"},
external_id="post-99",
)
New model field
ResourceSummary.external_id—str | None, populated from API responses
New types
BatchUpsertItem— input container withexternal_id,payload, and optionalcomponentBatchUpsertResult— output withsucceeded,failedlists andtotal,success_count,failure_count,has_failurespropertiesBatchItemError— error details withindex,external_id, andexception
Async support
All new methods are available on AsyncManagementClient with identical signatures.
Two new components that extend the LangChain integration beyond retrieval.
FoxNoseLoader
- Bulk document loading from FoxNose collections with cursor-based pagination
- Memory-efficient
lazy_load()for large collections - Async support via
alazy_load() - Filtering, sorting, and configurable batch size
create_foxnose_tool
- Factory function that wraps
FoxNoseRetrieveras a LangChain tool for LLM agents - Compatible with any LangChain agent framework (langgraph, etc.)
- Customizable tool name and description
- Two response formats: plain string or
(str, list[Document])tuple
from langchain_foxnose import FoxNoseLoader, create_foxnose_tool
# Bulk-load all documents from a folder
loader = FoxNoseLoader(
client=client,
folder_path="knowledge-base",
page_content_field="body",
)
docs = loader.load()
# Create a search tool for agents
tool = create_foxnose_tool(
client=client,
folder_path="knowledge-base",
page_content_field="body",
search_mode="hybrid",
)
Audit trail for all content changes — now accessible via API and dashboard.
Features
- Environment Events — track changes to collections, resources, revisions, components, versions, APIs, roles, and API keys
- Global Events — track changes to users and projects at the organization level
- Rich filtering by entity type, event type, time range, and related entities
- Actor information including user details or API key metadata
- Detailed metadata for each event type with contextual information
Dashboard
- Activity Log section added to environment dashboard — view and filter environment events
- Activity Log section added to organization dashboard — view and filter global events
Endpoints
GET /v1/:env/events/— List environment eventsGET /v1/global-events/— List global events
Initial release of the official LangChain integration for FoxNose.
Features
- FoxNoseRetriever — LangChain BaseRetriever backed by FoxNose Flux search
- Support for all search modes: text, vector, hybrid, and vector-boosted
- Flexible content mapping: single field, multiple fields, or custom mapper
- Metadata control: whitelist, blacklist, system metadata toggle
- Native async support via AsyncFluxClient
- Structured filtering via where parameter
- Convenience from_client_params() constructor
Installation
pip install langchain-foxnose
Quick Start
from langchain_openai import ChatOpenAI
from langchain.chains import RetrievalQA
from langchain_foxnose import FoxNoseRetriever
retriever = FoxNoseRetriever.from_client_params(
base_url="https://7c9h4pwu.fxns.io",
api_prefix="content",
public_key="YOUR_PUBLIC_KEY",
secret_key="YOUR_SECRET_KEY",
folder="articles",
search_mode="hybrid",
content_field="body",
)
qa = RetrievalQA.from_chain_type(
llm=ChatOpenAI(model="gpt-4o"),
retriever=retriever,
)
answer = qa.invoke("What are the best practices for API design?")
print(answer["result"])
Management client methods now accept either string keys or corresponding model objects (e.g. FolderSummary, ResourceSummary) wherever a *_key parameter is used. This eliminates the need to manually extract .key from objects returned by the API.
Added
- Model objects as identifiers — pass objects like
FolderSummaryorResourceSummarydirectly instead of extracting.key _resolve_key()helper for extracting string keys from model objects- 13 type aliases for method parameters:
FolderRef,ResourceRef,RevisionRef,ComponentRef,SchemaVersionRef,OrgRef,ProjectRef,EnvironmentRef,ManagementRoleRef,FluxRoleRef,ManagementAPIKeyRef,FluxAPIKeyRef,APIRef
from foxnose_sdk.management import ManagementClient
from foxnose_sdk.auth import SimpleKeyAuth
client = ManagementClient(
base_url="https://api.foxnose.net",
auth=SimpleKeyAuth("YOUR_PUBLIC_KEY", "YOUR_SECRET_KEY"),
)
# Before 0.2.0 — manually extract .key
collection = client.get_collection("my-folder")
resources = client.list_resources(collection.key)
# With 0.2.0 — pass the object directly
collection = client.get_collection("my-folder")
resources = client.list_resources(collection)
Initial release of the official FoxNose Python SDK.
Features
- Full support for Flux API v1
- Sync and async clients
- Simple and Secure authentication methods
- Type hints for all methods and responses
- Built-in error handling
Installation
pip install foxnose-sdk
Quick Start
from foxnose_sdk.flux import FluxClient
from foxnose_sdk.auth import SimpleKeyAuth
client = FluxClient(
base_url="https://7c9h4pwu.fxns.io",
api_prefix="content",
auth=SimpleKeyAuth("YOUR_PUBLIC_KEY", "YOUR_SECRET_KEY"),
)
# List resources
data = client.list_resources("articles")
for article in data["results"]:
print(f"- {article['data']['title']}")
# Search with filters
data = client.search("articles", body={
"where": {
"$": {
"all_of": [
{"status__eq": "published"},
{"category__in": ["tech", "science"]}
]
}
},
"limit": 10
})
# Vector search
data = client.search("articles", body={
"search_mode": "vector",
"vector_search": {
"query": "how to build AI applications",
"top_k": 5,
"similarity_threshold": 0.7
}
})
client.close()