All Integrations

FoxNose TypeScript SDK

Official TypeScript client for FoxNose Management and Flux APIs. Async-only, type-safe, with zero dependencies.

Version:LatestLicense:Apache 2.0

Overview

The FoxNose TypeScript SDK provides a convenient way to interact with FoxNose APIs from your Node.js applications. It includes two main clients:

  • ManagementClient — For administrative operations: managing collections, components, resources, roles, and API keys
  • FluxClient — For content delivery: fetching published resources, searching content, and accessing localized data

Features

  • Type-safe — Full TypeScript interfaces for all API responses
  • Async-only — Built on native fetch (Node 18+)
  • Automatic retries — Exponential backoff with jitter and Retry-After support
  • Four auth strategies — Anonymous, JWT, Simple key, and Secure (ECDSA P-256)
  • Zero dependencies — Uses only Node.js built-in modules
  • Dual output — ESM and CommonJS builds with full .d.ts declarations

Installation

npm install @foxnose/sdk
# or
pnpm add @foxnose/sdk

Quick Start

Management Client

import { ManagementClient, SimpleKeyAuth } from '@foxnose/sdk';

const client = new ManagementClient({
  baseUrl: 'https://api.foxnose.net',
  environmentKey: 'your-environment-key',
  auth: new SimpleKeyAuth('YOUR_PUBLIC_KEY', 'YOUR_SECRET_KEY'),
});

// List collections
const collections = await client.listCollections();
console.log(collections.results);

// Create a resource
const resource = await client.createResource('my-collection-key', {
  data: { title: 'Hello World' },
});

client.close();

Flux Client

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'),
});

// List resources
const resources = await client.listResources('articles');

// Vector search
const results = await client.search('articles', {
  search_mode: 'vector',
  vector_search: {
    query: 'how to build AI applications',
    top_k: 5,
  },
});

client.close();

Authentication

The SDK supports four authentication strategies:

StrategyUse case
SimpleKeyAuthDevelopment and Flux API access
SecureKeyAuthProduction — ECDSA P-256 signed requests
JWTAuthServer-side apps with user tokens
AnonymousAuthUnauthenticated endpoints
import { SimpleKeyAuth, SecureKeyAuth, JWTAuth, AnonymousAuth } from '@foxnose/sdk';

// Simple key pair
const simple = new SimpleKeyAuth('public-key', 'secret-key');

// ECDSA P-256 signature
const secure = new SecureKeyAuth('public-key', 'base64-der-private-key');

// JWT token
const jwt = JWTAuth.fromStaticToken('your-access-token');

// No authentication
const anon = new AnonymousAuth();

Batch Operations

Efficiently upsert multiple resources with concurrency control:

const items = [
  { external_id: 'article-1', payload: { data: { title: 'First' } } },
  { external_id: 'article-2', payload: { data: { title: 'Second' } } },
];

const result = await client.batchUpsertResources('my-collection-key', items, {
  maxConcurrency: 5,
  onProgress: (completed, total) => console.log(`${completed}/${total}`),
});

console.log(`OK: ${result.succeeded.length}, Failed: ${result.failed.length}`);

Components on Collections

Collections can embed Components as nested fields with explicit pin semantics (component, component_version, auto_update). The nestedFieldMeta helper builds the meta block with camelCase ergonomics, and syncCollectionComponent advances pinned fields to a target Component version on demand.

import { ManagementClient, JWTAuth, nestedFieldMeta } from '@foxnose/sdk';

const client = new ManagementClient({
  baseUrl: 'https://api.foxnose.net',
  environmentKey: 'prod',
  auth: JWTAuth.fromStaticToken('YOUR_ACCESS_TOKEN'),
});

// Embed a Component as a pinned nested field on a Collection draft.
await client.createCollectionField('articles', 'v2-draft', {
  key: 'seo',
  name: 'SEO',
  type: 'nested',
  required: true,
  meta: nestedFieldMeta({
    component: 'cmp-seo-metadata',
    componentVersion: 'ver-abc12345',
    autoUpdate: false, // default — pin until explicit sync
  }),
});

syncCollectionComponent

Advance pinned nested fields to a newer Component version. An empty body syncs every pinned field to its Component's current version; pass fieldPaths/toVersions to target specific paths.

// Advance every pinned nested field to its Component's current version.
const result = await client.syncCollectionComponent('articles');
console.log(result.synced_paths, result.schema_version);

// Advance specific paths to a chosen Component version.
await client.syncCollectionComponent('articles', {
  fieldPaths: ['seo'],
  toVersions: { seo: 'ver-def67890' },
});

syncCollectionComponent returns a SyncComponentResponse with synced_paths, skipped (per-path reasons), and schema_version (the UID of the newly published Collection schema version, or null if no field needed advancing). On a compatibility conflict the server returns 409 component_sync_conflict; quota exhaustion returns 422 too_many_versions. Both surface as FoxnoseAPIError.

Error Handling

All API errors are thrown as typed exceptions:

import { FoxnoseAPIError, FoxnoseTransportError } from '@foxnose/sdk';

try {
  await client.getResource('my-collection-key', 'nonexistent-key');
} catch (err) {
  if (err instanceof FoxnoseAPIError) {
    console.error(err.statusCode);  // 404
    console.error(err.errorCode);   // "not_found"
  } else if (err instanceof FoxnoseTransportError) {
    console.error('Network error:', err.message);
  }
}

Billing errors

Billing and quota responses are thrown as typed subclasses of FoxnoseAPIError, so any existing catch (err) { if (err instanceof FoxnoseAPIError) ... } keeps working. Narrow to a subclass to read its typed fields:

ClassHTTPFields
SpendCapExceededError402capUsd, cycleResetsAt, raiseCapUrl
PlanExhaustedError402axis, windowResetsAt, upgradeUrl
PlanLimitExceededError403entity, limit, current, upgradeUrl
RateLimitExceededError429retryAfter
import {
  FoxnoseAPIError,
  SpendCapExceededError,
  PlanExhaustedError,
  PlanLimitExceededError,
  RateLimitExceededError,
} from '@foxnose/sdk';

try {
  await client.createResource('my-collection-key', {
    data: { title: 'Hello World' },
  });
} catch (err) {
  if (err instanceof SpendCapExceededError) {
    // HTTP 402 — the account spend cap was reached.
    console.error(err.capUsd);        // Spend cap in USD (or null)
    console.error(err.cycleResetsAt); // ISO timestamp when the cycle resets
    console.error(err.raiseCapUrl);   // Where to raise the cap
  } else if (err instanceof PlanExhaustedError) {
    // HTTP 402 — a metered plan allowance ran out (Free plans).
    console.error(err.axis);          // e.g. "retrievals", "writes"
    console.error(err.windowResetsAt);
    console.error(err.upgradeUrl);
  } else if (err instanceof PlanLimitExceededError) {
    // HTTP 403 — a structural plan limit was hit.
    console.error(err.entity);        // e.g. "collections"
    console.error(err.current, err.limit);
    console.error(err.upgradeUrl);    // May be undefined
  } else if (err instanceof RateLimitExceededError) {
    // HTTP 429 — too many requests.
    console.error(err.retryAfter);    // Seconds to wait (from Retry-After)
  } else if (err instanceof FoxnoseAPIError) {
    console.error(err.statusCode, err.errorCode);
  }
}

All four subclass FoxnoseAPIError, so a single catch (err) { if (err instanceof FoxnoseAPIError) ... } still catches them when you don't need the typed fields.

Resources

Was this page helpful?