TypeScript SDK for the Saturation API
npm install @saturation-api/jsOfficial TypeScript SDK for the Saturation API. Build powerful integrations with your workspace data including projects, budgets, actuals, contacts, purchase orders, and attachments.



- 🚀 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
``bash`
npm install @saturation-api/jsor
yarn add @saturation-api/jsor
pnpm add @saturation-api/js
`typescript
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);
`
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
`typescript`
const client = new Saturation({
apiKey: process.env.SATURATION_API_KEY!,
});
All types are auto-generated from the OpenAPI specification and available for import:
`typescript
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);
`
Projects are the main organizational unit in Saturation. Each project contains budgets, actuals, purchase orders, and other related data.
`typescript
// 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',
});
`
Work with multi-phase budgets, including line items, accounts, subtotals, and markups.
`typescript
// 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'],
});
`
Manage different budget phases like estimate, revised, and actual.
`typescript
// 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',
});
`
Track actual expenses and sync with your accounting system.
`typescript
// 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
);
`
Manage purchase orders with line-item level detail.
`typescript
// 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'],
});
`
Manage vendors, crew members, and other contacts.
`typescript
// Create a contact
const contact = await client.createContact({
name: 'John Smith',
email: 'john@example.com',
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
);
`
Define custom rates for line items and contacts.
`typescript
// 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'],
});
`
Upload and manage file attachments.
`typescript
// 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);
`
The SDK works seamlessly in React applications. Here are some common patterns:
`tsx
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
const [loading, setLoading] = useState(true);
const [error, setError] = useState
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
return (
$3
`tsx
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(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
Loading budget...;
if (!budget) return No budget found; return (
Budget Overview
Total: ${budget.account.totals.estimate || 0}
Actual: ${budget.account.totals.actual || 0}
Budget Lines
Account
Description
Estimate
Actual
{budget.account.lines.map(line => (
{line.accountId}
{line.description}
${line.totals.estimate || 0}
${line.totals.actual || 0}
))}
);
}
`$3
`tsx
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({
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 (
);
}
`$3
`tsx
import React, { createContext, useContext, ReactNode } from 'react';
import { Saturation } from '@saturation-api/js';const SaturationContext = createContext(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 (
{children}
);
}
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 (
);
}
`$3
`tsx
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) => {
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 (
type="file"
onChange={handleFileSelect}
disabled={uploading}
accept=".pdf,.jpg,.jpeg,.png"
/>
{uploading && Uploading...}
);
}
`$3
For server-side usage in Next.js:
`typescript
// 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
$3
The SDK provides detailed error information with proper TypeScript types.
`typescript
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
}
`$3
Many endpoints support the
expands parameter to include related data in a single request.`typescript
// 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
],
});
`$3
The API supports using your existing account codes as identifiers.
`typescript
// 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',
});
`$3
Access public rate libraries shared across workspaces.
`typescript
// 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,
});
`$3
Create multiple items efficiently in a single request.
`typescript
// 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.
`typescript
import type {
Project,
Budget,
Actual,
PurchaseOrder,
Contact,
CreateProjectInput,
UpdateProjectInput,
ListProjectsParams,
} from '@saturation-api/js';// Type-safe function
async function getProjectBudgetTotal(projectId: string): Promise {
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.
$3
#### 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 attachmentDevelopment
$3
`bash
Clone the repository
git clone https://github.com/saturation-api/saturation-js.git
cd saturation-jsInstall dependencies
npm installGenerate types from OpenAPI spec
npm run generateRun tests
npm testBuild the package
npm run build
`$3
-
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$3
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`)MIT - see LICENSE file for details.
- Documentation
- API Reference
- GitHub Issues
- Support Email