9 min read
JavaScript / TypeScript
Official Node.js and browser library for DocuRift API
Integrate DocuRift document processing into your JavaScript and TypeScript applications. Works seamlessly in Node.js, browsers, and edge runtimes.
Installation
Install using your preferred package manager:
npm install docurift
# or
pnpm add docurift
# or
yarn add docurift
For projects without an official SDK, use the native fetch API:
# No additional dependencies needed for fetch-based integration
Quick Start
Using Fetch (Recommended)
const API_KEY = process.env.DOCURIFT_API_KEY;
const API_URL = 'https://api.docurift.com/v1';
async function processDocument(file: File) {
const formData = new FormData();
formData.append('file', file);
formData.append('documentType', 'invoice');
const response = await fetch(`${API_URL}/documents/process/sync`, {
method: 'POST',
headers: {
'X-API-Key': API_KEY,
},
body: formData,
});
if (!response.ok) {
const error = await response.json();
throw new Error(error.error?.message || 'Processing failed');
}
return response.json();
}
Browser Usage
Process documents directly from browser file inputs:
HTML File Input
<input type="file" id="document-input" accept=".pdf,.jpg,.jpeg,.png" />
<button onclick="uploadDocument()">Process Document</button>
<div id="result"></div>
JavaScript Handler
const API_URL = 'https://api.docurift.com/v1';
async function uploadDocument() {
const fileInput = document.getElementById('document-input');
const resultDiv = document.getElementById('result');
if (!fileInput.files.length) {
resultDiv.textContent = 'Please select a file';
return;
}
const file = fileInput.files[0];
// Validate file size (10MB limit)
if (file.size > 10 * 1024 * 1024) {
resultDiv.textContent = 'File size exceeds 10MB limit';
return;
}
const formData = new FormData();
formData.append('file', file);
formData.append('documentType', 'invoice');
try {
resultDiv.textContent = 'Processing...';
const response = await fetch(`${API_URL}/documents/process/sync`, {
method: 'POST',
headers: {
// API key should be proxied through your backend in production
'X-API-Key': 'YOUR_API_KEY',
},
body: formData,
});
if (!response.ok) {
const error = await response.json();
throw new Error(error.error?.message || 'Processing failed');
}
const result = await response.json();
resultDiv.textContent = JSON.stringify(result.data, null, 2);
} catch (error) {
resultDiv.textContent = `Error: ${error.message}`;
}
}
React Component Example
import { useState, useCallback } from 'react';
interface ProcessingResult {
documentId: string;
status: string;
result: Record<string, unknown>;
}
export function DocumentUploader() {
const [file, setFile] = useState<File | null>(null);
const [result, setResult] = useState<ProcessingResult | null>(null);
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const handleFileChange = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
const selectedFile = e.target.files?.[0];
if (selectedFile) {
setFile(selectedFile);
setError(null);
}
}, []);
const processDocument = useCallback(async () => {
if (!file) return;
setLoading(true);
setError(null);
const formData = new FormData();
formData.append('file', file);
formData.append('documentType', 'invoice');
try {
// Use your backend API route to proxy requests
const response = await fetch('/api/process-document', {
method: 'POST',
body: formData,
});
if (!response.ok) {
const errorData = await response.json();
throw new Error(errorData.error?.message || 'Processing failed');
}
const data = await response.json();
setResult(data.data);
} catch (err) {
setError(err instanceof Error ? err.message : 'Unknown error');
} finally {
setLoading(false);
}
}, [file]);
return (
<div className="document-uploader">
<input
type="file"
accept=".pdf,.jpg,.jpeg,.png"
onChange={handleFileChange}
disabled={loading}
/>
<button onClick={processDocument} disabled={!file || loading}>
{loading ? 'Processing...' : 'Process Document'}
</button>
{error && <div className="error">{error}</div>}
{result && (
<div className="result">
<h3>Document ID: {result.documentId}</h3>
<pre>{JSON.stringify(result.result, null, 2)}</pre>
</div>
)}
</div>
);
}
Node.js Usage
Using fs.createReadStream
import fs from 'fs';
import path from 'path';
const API_KEY = process.env.DOCURIFT_API_KEY;
const API_URL = 'https://api.docurift.com/v1';
async function processDocument(filePath: string, documentType: string = 'general') {
const fileBuffer = fs.readFileSync(filePath);
const fileName = path.basename(filePath);
const formData = new FormData();
formData.append('file', new Blob([fileBuffer]), fileName);
formData.append('documentType', documentType);
const response = await fetch(`${API_URL}/documents/process/sync`, {
method: 'POST',
headers: {
'X-API-Key': API_KEY!,
},
body: formData,
});
if (!response.ok) {
const error = await response.json();
throw new Error(error.error?.message || `HTTP ${response.status}`);
}
return response.json();
}
// Usage
const result = await processDocument('./invoice.pdf', 'invoice');
console.log('Document ID:', result.data.documentId);
console.log('Extracted data:', result.data.result);
Using node-fetch (Legacy Node.js)
import fetch from 'node-fetch';
import FormData from 'form-data';
import fs from 'fs';
const API_KEY = process.env.DOCURIFT_API_KEY;
const API_URL = 'https://api.docurift.com/v1';
async function processDocument(filePath: string, documentType: string = 'general') {
const form = new FormData();
form.append('file', fs.createReadStream(filePath));
form.append('documentType', documentType);
const response = await fetch(`${API_URL}/documents/process/sync`, {
method: 'POST',
headers: {
'X-API-Key': API_KEY!,
...form.getHeaders(),
},
body: form,
});
if (!response.ok) {
const error = await response.json();
throw new Error(error.error?.message || `HTTP ${response.status}`);
}
return response.json();
}
TypeScript Types
Use these type definitions for type-safe integration:
// types/docurift.ts
export interface DocuRiftConfig {
apiKey: string;
baseUrl?: string;
timeout?: number;
}
export type DocumentType = 'invoice' | 'receipt' | 'contract' | 'bill_of_lading' | 'packing_list' | 'general';
export type ProcessingStatus = 'pending' | 'processing' | 'completed' | 'failed';
export interface ProcessingOptions {
documentType?: DocumentType;
webhookUrl?: string;
priority?: 'low' | 'normal' | 'high';
}
export interface Vendor {
name: string;
address?: string;
taxId?: string;
email?: string;
phone?: string;
}
export interface LineItem {
description: string;
quantity: number;
unitPrice: number;
total: number;
sku?: string;
}
export interface InvoiceData {
invoiceNumber: string;
invoiceDate: string;
dueDate?: string;
vendor: Vendor;
customer?: Vendor;
lineItems: LineItem[];
subtotal: number;
taxRate?: number;
taxAmount?: number;
totalAmount: number;
currency: string;
paymentTerms?: string;
notes?: string;
}
export interface ProcessingResult<T = Record<string, unknown>> {
documentId: string;
status: ProcessingStatus;
documentType: DocumentType;
confidence: number;
pagesProcessed: number;
processedAt: string;
result: T;
}
export interface JobStatus {
jobId: string;
documentId?: string;
status: ProcessingStatus;
progress?: number;
error?: string;
result?: Record<string, unknown>;
createdAt: string;
completedAt?: string;
}
export interface CreditBalance {
balance: number;
used: number;
limit: number;
resetsAt?: string;
}
export interface ApiResponse<T> {
success: boolean;
data: T;
}
export interface ApiError {
success: false;
error: {
code: string;
message: string;
details?: Record<string, unknown>;
};
}
export type ErrorCode =
| 'UNAUTHORIZED'
| 'INVALID_API_KEY'
| 'IP_BLOCKED'
| 'INSUFFICIENT_CREDITS'
| 'RATE_LIMIT_EXCEEDED'
| 'INVALID_FILE_TYPE'
| 'FILE_TOO_LARGE'
| 'PROCESSING_FAILED'
| 'NOT_FOUND'
| 'VALIDATION_ERROR';
Type-Safe Client
import type {
DocuRiftConfig,
ProcessingOptions,
ProcessingResult,
JobStatus,
CreditBalance,
ApiResponse,
ApiError,
InvoiceData,
} from './types/docurift';
class DocuRiftClient {
private apiKey: string;
private baseUrl: string;
private timeout: number;
constructor(config: DocuRiftConfig) {
this.apiKey = config.apiKey;
this.baseUrl = config.baseUrl || 'https://api.docurift.com/v1';
this.timeout = config.timeout || 120000;
}
private async request<T>(
endpoint: string,
options: RequestInit = {}
): Promise<T> {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), this.timeout);
try {
const response = await fetch(`${this.baseUrl}${endpoint}`, {
...options,
headers: {
'X-API-Key': this.apiKey,
...options.headers,
},
signal: controller.signal,
});
const data = await response.json();
if (!response.ok) {
const error = data as ApiError;
throw new DocuRiftError(
error.error.message,
error.error.code,
response.status
);
}
return (data as ApiResponse<T>).data;
} finally {
clearTimeout(timeoutId);
}
}
async processSync(
file: File | Buffer,
options: ProcessingOptions = {}
): Promise<ProcessingResult> {
const formData = new FormData();
if (file instanceof Buffer) {
formData.append('file', new Blob([file]), 'document.pdf');
} else {
formData.append('file', file);
}
if (options.documentType) {
formData.append('documentType', options.documentType);
}
return this.request<ProcessingResult>('/documents/process/sync', {
method: 'POST',
body: formData,
});
}
async processAsync(
file: File | Buffer,
options: ProcessingOptions = {}
): Promise<{ jobId: string }> {
const formData = new FormData();
if (file instanceof Buffer) {
formData.append('file', new Blob([file]), 'document.pdf');
} else {
formData.append('file', file);
}
if (options.documentType) {
formData.append('documentType', options.documentType);
}
if (options.webhookUrl) {
formData.append('webhookUrl', options.webhookUrl);
}
return this.request<{ jobId: string }>('/documents/process/async', {
method: 'POST',
body: formData,
});
}
async getJobStatus(jobId: string): Promise<JobStatus> {
return this.request<JobStatus>(`/jobs/${jobId}`);
}
async waitForCompletion(
jobId: string,
options: { maxWait?: number; pollInterval?: number } = {}
): Promise<ProcessingResult> {
const { maxWait = 300000, pollInterval = 2000 } = options;
const startTime = Date.now();
while (Date.now() - startTime < maxWait) {
const job = await this.getJobStatus(jobId);
if (job.status === 'completed' && job.result) {
return {
documentId: job.documentId!,
status: job.status,
result: job.result,
} as ProcessingResult;
}
if (job.status === 'failed') {
throw new DocuRiftError(
job.error || 'Processing failed',
'PROCESSING_FAILED',
500
);
}
await new Promise((resolve) => setTimeout(resolve, pollInterval));
}
throw new DocuRiftError(
`Job ${jobId} did not complete within timeout`,
'TIMEOUT',
408
);
}
async getDocument(documentId: string): Promise<ProcessingResult> {
return this.request<ProcessingResult>(`/documents/${documentId}`);
}
async listDocuments(params: { limit?: number; offset?: number } = {}): Promise<ProcessingResult[]> {
const searchParams = new URLSearchParams();
if (params.limit) searchParams.set('limit', String(params.limit));
if (params.offset) searchParams.set('offset', String(params.offset));
const query = searchParams.toString();
return this.request<ProcessingResult[]>(`/documents${query ? `?${query}` : ''}`);
}
async getCreditBalance(): Promise<CreditBalance> {
return this.request<CreditBalance>('/credits/balance');
}
}
class DocuRiftError extends Error {
constructor(
message: string,
public code: string,
public statusCode: number
) {
super(message);
this.name = 'DocuRiftError';
}
}
export { DocuRiftClient, DocuRiftError };
Error Handling
Handle API errors gracefully with typed error handling:
import { DocuRiftClient, DocuRiftError } from './docurift-client';
const client = new DocuRiftClient({
apiKey: process.env.DOCURIFT_API_KEY!,
});
async function processWithErrorHandling(file: File) {
try {
const result = await client.processSync(file, {
documentType: 'invoice',
});
console.log('Success:', result);
return result;
} catch (error) {
if (error instanceof DocuRiftError) {
switch (error.code) {
case 'UNAUTHORIZED':
case 'INVALID_API_KEY':
console.error('Authentication failed. Check your API key.');
break;
case 'INSUFFICIENT_CREDITS':
console.error('Insufficient credits. Please add credits to continue.');
break;
case 'RATE_LIMIT_EXCEEDED':
console.error('Rate limit exceeded. Please wait and retry.');
break;
case 'INVALID_FILE_TYPE':
console.error('Invalid file type. Use PDF, JPG, or PNG.');
break;
case 'FILE_TOO_LARGE':
console.error('File too large. Maximum size is 10MB.');
break;
case 'IP_BLOCKED':
console.error('IP blocked due to multiple failed attempts.');
break;
default:
console.error(`API error: ${error.message} (${error.code})`);
}
} else if (error instanceof Error) {
if (error.name === 'AbortError') {
console.error('Request timed out. Try async processing for large documents.');
} else {
console.error('Unexpected error:', error.message);
}
}
throw error;
}
}
Retry Logic
async function processWithRetry(
file: File,
maxRetries: number = 3,
backoffMs: number = 1000
): Promise<ProcessingResult> {
let lastError: Error | undefined;
for (let attempt = 0; attempt < maxRetries; attempt++) {
try {
return await client.processSync(file, { documentType: 'invoice' });
} catch (error) {
lastError = error as Error;
if (error instanceof DocuRiftError) {
// Don't retry on client errors
if (error.statusCode >= 400 && error.statusCode < 500) {
throw error;
}
}
// Exponential backoff
const delay = backoffMs * Math.pow(2, attempt);
console.log(`Attempt ${attempt + 1} failed, retrying in ${delay}ms...`);
await new Promise((resolve) => setTimeout(resolve, delay));
}
}
throw lastError;
}
Next.js API Route Example
Proxy API requests through your backend to protect your API key:
// app/api/process-document/route.ts
import { NextRequest, NextResponse } from 'next/server';
const API_KEY = process.env.DOCURIFT_API_KEY;
const API_URL = 'https://api.docurift.com/v1';
export async function POST(request: NextRequest) {
if (!API_KEY) {
return NextResponse.json(
{ error: { message: 'API key not configured' } },
{ status: 500 }
);
}
try {
const formData = await request.formData();
const response = await fetch(`${API_URL}/documents/process/sync`, {
method: 'POST',
headers: {
'X-API-Key': API_KEY,
},
body: formData,
});
const data = await response.json();
return NextResponse.json(data, { status: response.status });
} catch (error) {
console.error('Processing error:', error);
return NextResponse.json(
{ error: { message: 'Processing failed' } },
{ status: 500 }
);
}
}
Next Steps
- Authentication Guide - Detailed authentication options
- API Reference - Full endpoint documentation
- Webhooks Setup - Receive notifications for async processing
- React Integration Guide - Advanced React patterns