npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@saturation-api/js

v0.1.1

Published

TypeScript SDK for the Saturation API

Downloads

166

Readme

@saturation-api/js

Official TypeScript SDK for the Saturation API. Build powerful integrations with your workspace data including projects, budgets, actuals, contacts, purchase orders, and attachments.

npm version CI License: MIT

Features

  • 🚀 Full TypeScript Support - Auto-generated types from OpenAPI specification
  • 📦 Universal JavaScript - Works in Node.js, browsers, and React Native
  • 🌐 Browser Compatible - CORS-friendly with native fetch API support
  • 🔄 Real-time Collaboration - Changes appear instantly in the web app
  • 🎯 Type-Safe - Complete type coverage for all API endpoints
  • 🛠️ Developer Friendly - Intuitive API with comprehensive documentation
  • Lightweight - Minimal dependencies, optimized bundle size

Installation

npm install @saturation-api/js
# or
yarn add @saturation-api/js
# or
pnpm add @saturation-api/js

Quick Start

import { Saturation, type Project, type Budget } from '@saturation-api/js';

// Initialize the client
const client = new Saturation({
  apiKey: 'YOUR_API_KEY',
  baseURL: 'https://api.saturation.io/api/v1', // Optional, this is the default
});

// List all projects
const { projects } = await client.listProjects({ status: 'active' });
console.log('Active projects:', projects);

// Get a specific project with its budget
const budget: Budget = await client.getProjectBudget('nike-swoosh-commercial', {
  expands: ['phases', 'fringes', 'lines.contact', 'lines.phaseData'],
});
console.log('Project budget:', budget);

Authentication

Get your API key from the Saturation web app:

  1. Go to Settings → API Keys
  2. Create a new API key
  3. Copy the key and use it in your application
const client = new Saturation({
  apiKey: process.env.SATURATION_API_KEY!,
});

TypeScript Support

All types are auto-generated from the OpenAPI specification and available for import:

import { 
  Saturation,
  type Project,
  type Budget,
  type BudgetLine,
  type Actual,
  type PurchaseOrder,
  type Contact,
  type CreateProjectInput,
  type UpdateProjectInput,
  type ListProjectsData,
  // ... and many more
} from '@saturation-api/js';

// Use types for better type safety
function processProject(project: Project): void {
  console.log(`Processing ${project.name}`);
}

// Use query parameter types
const params: ListProjectsData['query'] = {
  status: 'active',
  labels: ['nike'],
};
const { projects } = await client.listProjects(params);

Core Concepts

Projects

Projects are the main organizational unit in Saturation. Each project contains budgets, actuals, purchase orders, and other related data.

// Create a new project
const project = await client.createProject({
  name: 'Nike Holiday Commercial',
  icon: '🎬',
  spaceId: 'commercial-productions',
  status: 'active',
  labels: ['nike', 'q4-2024', 'commercial'],
});

// Update project details
const updated = await client.updateProject(project.id, {
  status: 'archived',
  labels: ['completed', 'q4-2024'],
});

// List projects with filters
const { projects } = await client.listProjects({
  status: 'active',
  labels: ['nike', 'commercial'],
  spaceId: 'commercial-productions',
});

Budget Management

Work with multi-phase budgets, including line items, accounts, subtotals, and markups.

// Get complete budget with all details
const budget = await client.getProjectBudget('my-project', {
  expands: ['phases', 'fringes', 'globals', 'lines.contact'],
  idMode: 'user', // Use human-readable IDs
});

// Add budget lines
const updatedBudget = await client.createBudgetLines('my-project', {
  accountId: 'root',
  lines: [
    {
      type: 'line',
      accountId: '1100',
      description: 'Director',
      phaseData: {
        estimate: { amount: 50000 },
        actual: { amount: 48000 },
      },
    },
    {
      type: 'account',
      accountId: '2000',
      description: 'Camera Department',
    },
  ],
});

// Update a specific budget line
const line = await client.updateBudgetLine('my-project', '1100-DIRECTOR', {
  description: 'Director - Extended Cut',
  tags: ['above-the-line', 'key-personnel'],
});

Budget Phases

Manage different budget phases like estimate, revised, and actual.

// Create a new phase
const phase = await client.createBudgetPhase('my-project', {
  name: 'revised',
  label: 'Revised Budget',
  color: '#FFA500',
  order: 2,
});

// List all phases
const { phases } = await client.listBudgetPhases('my-project');

// Update phase details
await client.updateBudgetPhase('my-project', phase.id, {
  label: 'Revised Budget v2',
});

Actuals Tracking

Track actual expenses and sync with your accounting system.

// Create an actual
const actual = await client.createActual('my-project', {
  lineItemId: '2150-CAMERA',
  amount: 35000,
  date: '2024-03-20',
  description: 'RED Camera Package Rental',
  contactId: 'vendor-123',
  tags: ['equipment', 'week-2'],
});

// List actuals with filters
const { actuals } = await client.listProjectActuals('my-project', {
  dateFrom: '2024-03-01',
  dateTo: '2024-03-31',
  lineItemId: ['2150-CAMERA', '2160-LENSES'],
  expands: ['contact', 'attachments'],
});

// Upload attachment to actual
const attachment = await client.uploadActualAttachment(
  'my-project',
  actual.id,
  fileBuffer
);

Purchase Orders

Manage purchase orders with line-item level detail.

// Create a purchase order
const po = await client.createPurchaseOrder('my-project', {
  number: 'PO-001',
  contactId: 'vendor-456',
  date: '2024-03-15',
  items: [
    {
      lineItemId: '2150-CAMERA',
      amount: 35000,
      description: 'Camera equipment rental - 5 days',
    },
    {
      lineItemId: '2160-LENSES',
      amount: 8000,
      description: 'Lens kit rental',
    },
  ],
  tags: ['equipment', 'approved'],
});

// List purchase orders
const { purchaseOrders } = await client.listPurchaseOrders('my-project', {
  contactId: 'vendor-456',
  hasAttachments: true,
  expands: ['items.lineItem', 'contact'],
});

Contacts Management

Manage vendors, crew members, and other contacts.

// Create a contact
const contact = await client.createContact({
  name: 'John Smith',
  email: '[email protected]',
  phone: '+1234567890',
  type: 'individual',
  companyName: 'Smith Productions',
  address: '123 Main St, Los Angeles, CA 90001',
});

// Search contacts
const { contacts } = await client.listContacts({
  name: 'Smith',
  type: 'individual',
  companyName: 'Productions',
});

// Upload tax documents
const taxDoc = await client.uploadContactTaxDocument(
  contact.id,
  w9Buffer
);

Workspace Rates

Define custom rates for line items and contacts.

// Create a rate
const rate = await client.createWorkspaceRate({
  lineItemId: '1100-DIRECTOR',
  contactId: 'contact-789',
  rate: 1500,
  unit: 'day',
  currency: 'USD',
  tags: ['union', 'tier-1'],
});

// List rates with filters
const { rates } = await client.listWorkspaceRates({
  lineItemId: '1100-DIRECTOR',
  tags: ['union'],
});

File Uploads

Upload and manage file attachments.

// Upload a file
const upload = await client.uploadFile(fileBuffer, 'document.pdf');

// Download a file
const fileContent = await client.downloadFile(upload.id);

// Delete a file
await client.deleteFile(upload.id);

React Examples

The SDK works seamlessly in React applications. Here are some common patterns:

Basic React Hook

import { useState, useEffect } from 'react';
import { Saturation, Project } from '@saturation-api/js';

const client = new Saturation({
  apiKey: process.env.REACT_APP_SATURATION_API_KEY!,
});

function useProjects() {
  const [projects, setProjects] = useState<Project[]>([]);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState<Error | null>(null);

  useEffect(() => {
    client.listProjects({ status: 'active' })
      .then(({ projects }) => setProjects(projects))
      .catch(setError)
      .finally(() => setLoading(false));
  }, []);

  return { projects, loading, error };
}

// Usage in component
function ProjectList() {
  const { projects, loading, error } = useProjects();

  if (loading) return <div>Loading projects...</div>;
  if (error) return <div>Error: {error.message}</div>;

  return (
    <ul>
      {projects.map(project => (
        <li key={project.id}>
          {project.icon} {project.name}
        </li>
      ))}
    </ul>
  );
}

Budget Dashboard Component

import React, { useState, useEffect } from 'react';
import { Saturation, Budget } from '@saturation-api/js';

interface BudgetDashboardProps {
  projectId: string;
  apiKey: string;
}

export function BudgetDashboard({ projectId, apiKey }: BudgetDashboardProps) {
  const [budget, setBudget] = useState<Budget | null>(null);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    const client = new Saturation({ apiKey });

    client.getProjectBudget(projectId, {
      expands: ['phases', 'fringes', 'lines.contact'],
    })
      .then(setBudget)
      .catch(console.error)
      .finally(() => setLoading(false));
  }, [projectId, apiKey]);

  if (loading) return <div>Loading budget...</div>;
  if (!budget) return <div>No budget found</div>;

  return (
    <div className="budget-dashboard">
      <h2>Budget Overview</h2>
      <div className="budget-totals">
        <div>Total: ${budget.account.totals.estimate || 0}</div>
        <div>Actual: ${budget.account.totals.actual || 0}</div>
      </div>
      
      <h3>Budget Lines</h3>
      <table>
        <thead>
          <tr>
            <th>Account</th>
            <th>Description</th>
            <th>Estimate</th>
            <th>Actual</th>
          </tr>
        </thead>
        <tbody>
          {budget.account.lines.map(line => (
            <tr key={line.id}>
              <td>{line.accountId}</td>
              <td>{line.description}</td>
              <td>${line.totals.estimate || 0}</td>
              <td>${line.totals.actual || 0}</td>
            </tr>
          ))}
        </tbody>
      </table>
    </div>
  );
}

Create Actual Form

import React, { useState } from 'react';
import { Saturation, CreateActualInput } from '@saturation-api/js';

interface ActualFormProps {
  projectId: string;
  client: Saturation;
  onSuccess: () => void;
}

export function CreateActualForm({ projectId, client, onSuccess }: ActualFormProps) {
  const [formData, setFormData] = useState<CreateActualInput>({
    lineItemId: '',
    amount: 0,
    date: new Date().toISOString().split('T')[0],
    description: '',
  });
  const [submitting, setSubmitting] = useState(false);

  const handleSubmit = async (e: React.FormEvent) => {
    e.preventDefault();
    setSubmitting(true);

    try {
      await client.createActual(projectId, formData);
      onSuccess();
      // Reset form
      setFormData({
        lineItemId: '',
        amount: 0,
        date: new Date().toISOString().split('T')[0],
        description: '',
      });
    } catch (error) {
      console.error('Failed to create actual:', error);
    } finally {
      setSubmitting(false);
    }
  };

  return (
    <form onSubmit={handleSubmit}>
      <input
        type="text"
        placeholder="Line Item ID (e.g., 2150-CAMERA)"
        value={formData.lineItemId}
        onChange={(e) => setFormData({ ...formData, lineItemId: e.target.value })}
        required
      />
      
      <input
        type="number"
        placeholder="Amount"
        value={formData.amount}
        onChange={(e) => setFormData({ ...formData, amount: Number(e.target.value) })}
        required
      />
      
      <input
        type="date"
        value={formData.date}
        onChange={(e) => setFormData({ ...formData, date: e.target.value })}
        required
      />
      
      <textarea
        placeholder="Description"
        value={formData.description}
        onChange={(e) => setFormData({ ...formData, description: e.target.value })}
      />
      
      <button type="submit" disabled={submitting}>
        {submitting ? 'Creating...' : 'Create Actual'}
      </button>
    </form>
  );
}

React Context Provider

import React, { createContext, useContext, ReactNode } from 'react';
import { Saturation } from '@saturation-api/js';

const SaturationContext = createContext<Saturation | null>(null);

interface SaturationProviderProps {
  apiKey: string;
  baseURL?: string;
  children: ReactNode;
}

export function SaturationProvider({ apiKey, baseURL, children }: SaturationProviderProps) {
  const client = React.useMemo(
    () => new Saturation({ apiKey, baseURL }),
    [apiKey, baseURL]
  );

  return (
    <SaturationContext.Provider value={client}>
      {children}
    </SaturationContext.Provider>
  );
}

export function useSaturation() {
  const client = useContext(SaturationContext);
  if (!client) {
    throw new Error('useSaturation must be used within a SaturationProvider');
  }
  return client;
}

// Usage in your app
function App() {
  return (
    <SaturationProvider apiKey={process.env.REACT_APP_SATURATION_API_KEY!}>
      <YourComponents />
    </SaturationProvider>
  );
}

File Upload Component

import React, { useState } from 'react';
import { useSaturation } from './SaturationProvider';

interface FileUploadProps {
  projectId: string;
  actualId: string;
  onUploadComplete: () => void;
}

export function FileUpload({ projectId, actualId, onUploadComplete }: FileUploadProps) {
  const client = useSaturation();
  const [uploading, setUploading] = useState(false);

  const handleFileSelect = async (e: React.ChangeEvent<HTMLInputElement>) => {
    const file = e.target.files?.[0];
    if (!file) return;

    setUploading(true);
    try {
      // Convert File to Blob for the SDK
      const blob = new Blob([await file.arrayBuffer()], { type: file.type });
      
      await client.uploadActualAttachment(
        projectId,
        actualId,
        blob,
        file.name
      );
      
      onUploadComplete();
    } catch (error) {
      console.error('Upload failed:', error);
    } finally {
      setUploading(false);
    }
  };

  return (
    <div>
      <input
        type="file"
        onChange={handleFileSelect}
        disabled={uploading}
        accept=".pdf,.jpg,.jpeg,.png"
      />
      {uploading && <span>Uploading...</span>}
    </div>
  );
}

Next.js API Route

For server-side usage in Next.js:

// pages/api/projects.ts
import type { NextApiRequest, NextApiResponse } from 'next';
import { Saturation } from '@saturation-api/js';

const client = new Saturation({
  apiKey: process.env.SATURATION_API_KEY!,
});

export default async function handler(
  req: NextApiRequest,
  res: NextApiResponse
) {
  try {
    if (req.method === 'GET') {
      const { projects } = await client.listProjects({ status: 'active' });
      res.status(200).json(projects);
    } else if (req.method === 'POST') {
      const project = await client.createProject(req.body);
      res.status(201).json(project);
    } else {
      res.status(405).json({ error: 'Method not allowed' });
    }
  } catch (error) {
    console.error('API error:', error);
    res.status(500).json({ error: 'Internal server error' });
  }
}

Advanced Usage

Error Handling

The SDK provides detailed error information with proper TypeScript types.

try {
  const project = await client.getProject('non-existent');
} catch (error) {
  console.error('API Error:', error);
  // The generated client handles errors internally
  // Check error.response for details if available
}

Using Expands

Many endpoints support the expands parameter to include related data in a single request.

// Get budget with all related data
const budget = await client.getProjectBudget('my-project', {
  expands: [
    'phases',           // Include all budget phases
    'fringes',          // Include fringe calculations
    'globals',          // Include global calculations
    'lines.contact',    // Include contact info for each line
    'lines.phaseData',  // Include phase-specific data
  ],
});

// Get actuals with full details
const { actuals } = await client.listProjectActuals('my-project', {
  expands: [
    'contact',          // Include contact information
    'attachments',      // Include file attachments
    'lineItem',         // Include budget line details
  ],
});

Using Your Account Codes

The API supports using your existing account codes as identifiers.

// Use your account codes directly
const line = await client.getBudgetLine('my-project', '1100-LABOR');
const updated = await client.updateBudgetLine('my-project', '2150-CAMERA', {
  description: 'Camera Equipment - Updated',
});

// Create actuals with your codes
await client.createActual('my-project', {
  lineItemId: '2150-CAMERA',
  amount: 5000,
  date: '2024-03-20',
});

Public Rates

Access public rate libraries shared across workspaces.

// List all public ratepacks
const { ratepacks } = await client.listPublicRatepacks();

// Search for specific ratepacks
const { ratepacks: iatseRatepacks } = await client.listPublicRatepacks({
  search: 'IATSE',
});

// Get all rates from a specific ratepack
const { rates } = await client.getPublicRates('ratepack-123', {
  search: 'grip',
  includeArchived: false,
});

Batch Operations

Create multiple items efficiently in a single request.

// Create multiple budget lines at once
await client.createBudgetLines('my-project', {
  accountId: 'root',
  lines: [
    { type: 'account', accountId: '1000', description: 'Above The Line' },
    { type: 'line', accountId: '1100', description: 'Producer' },
    { type: 'line', accountId: '1200', description: 'Director' },
    { type: 'subtotal', description: 'Total ATL' },
    { type: 'account', accountId: '2000', description: 'Below The Line' },
    { type: 'line', accountId: '2100', description: 'Camera' },
    { type: 'line', accountId: '2200', description: 'Lighting' },
  ],
});

TypeScript Support

The SDK is fully typed with TypeScript. All request and response types are auto-generated from the OpenAPI specification.

import type {
  Project,
  Budget,
  Actual,
  PurchaseOrder,
  Contact,
  CreateProjectInput,
  UpdateProjectInput,
  ListProjectsParams,
} from '@saturation-api/js';

// Type-safe function
async function getProjectBudgetTotal(projectId: string): Promise<number> {
  const budget = await client.getProjectBudget(projectId);
  return budget.account.totals.estimate || 0;
}

// Type-safe error handling
function isNotFoundError(error: unknown): boolean {
  return error instanceof SaturationError && error.statusCode === 404;
}

API Reference

For complete API documentation, see the API Reference.

Available Methods

Projects

  • listProjects(params?) - List all projects
  • getProject(projectId) - Get a specific project
  • createProject(data) - Create a new project
  • updateProject(projectId, data) - Update project details
  • deleteProject(projectId) - Delete a project

Budget

  • getProjectBudget(projectId, params?) - Get project budget
  • createBudgetLines(projectId, data) - Add budget lines
  • getBudgetLine(projectId, lineId, params?) - Get specific line
  • updateBudgetLine(projectId, lineId, data) - Update budget line
  • deleteBudgetLine(projectId, lineId) - Delete budget line

Phases

  • listBudgetPhases(projectId) - List budget phases
  • createBudgetPhase(projectId, data) - Create phase
  • getBudgetPhase(projectId, phaseId) - Get phase details
  • updateBudgetPhase(projectId, phaseId, data) - Update phase
  • deleteBudgetPhase(projectId, phaseId) - Delete phase

Actuals

  • listProjectActuals(projectId, params?) - List actuals
  • getActual(projectId, actualId, params?) - Get actual details
  • createActual(projectId, data) - Create actual
  • batchCreateActuals(projectId, data) - Batch create actuals
  • updateActual(projectId, actualId, data) - Update actual
  • deleteActual(projectId, actualId) - Delete actual
  • uploadActualAttachment(projectId, actualId, file, filename) - Add attachment

Purchase Orders

  • listPurchaseOrders(projectId, params?) - List purchase orders
  • getPurchaseOrder(projectId, poId, params?) - Get PO details
  • createPurchaseOrder(projectId, data) - Create purchase order
  • updatePurchaseOrder(projectId, poId, data) - Update PO
  • deletePurchaseOrder(projectId, poId) - Delete PO
  • uploadPurchaseOrderAttachment(projectId, poId, file, filename) - Add attachment

Contacts

  • listContacts(params?) - List contacts
  • createContact(data) - Create contact
  • getContact(contactId) - Get contact details
  • updateContact(contactId, data) - Update contact
  • uploadContactTaxDocument(contactId, file, filename) - Upload tax doc
  • uploadContactAttachment(contactId, file, filename) - Add attachment

Development

Setup

# Clone the repository
git clone https://github.com/saturation-api/saturation-js.git
cd saturation-js

# Install dependencies
npm install

# Generate types from OpenAPI spec
npm run generate

# Run tests
npm test

# Build the package
npm run build

Scripts

  • npm run build - Build for production
  • npm run dev - Watch mode for development
  • npm test - Run tests
  • npm run lint - Lint code
  • npm run format - Format code with Prettier
  • npm run generate - Generate types from OpenAPI spec

Contributing

  1. Fork the repository
  2. Create your feature branch (git checkout -b feature/amazing-feature)
  3. Commit your changes (git commit -m 'Add amazing feature')
  4. Push to the branch (git push origin feature/amazing-feature)
  5. Open a Pull Request

License

MIT - see LICENSE file for details.

Support