Skip to content

datashift-io/typescript-sdk

Repository files navigation

@datashift/sdk

npm version License: MIT

TypeScript SDK for integrating human-in-the-loop checkpoints into AI agent workflows.

Overview

Datashift enables AI agents to submit tasks for human review before committing changes. Create review queues, define review types (approval, classification, labeling, scoring, augmentation), and integrate human oversight into any AI workflow — via REST API or MCP.

Installation

npm install @datashift/sdk

Quick Start

import {DatashiftRestClient} from '@datashift/sdk';

const datashift = new DatashiftRestClient({
    apiKey: process.env.DATASHIFT_API_KEY!,
});

// AI agent enriches company data from external sources
const enrichedData = {
    companyId: 'acme_corp_123',
    original: {name: 'Acme Corp', industry: 'Unknown'},
    enriched: {
        name: 'Acme Corporation',
        industry: 'Manufacturing',
        employees: 5200,
        revenue: '$1.2B',
        linkedIn: 'https://linkedin.com/company/acme',
    },
    sources: ['LinkedIn', 'Crunchbase', 'SEC Filings'],
};

// Submit enrichment for human verification before updating CRM
const task = await datashift.task.submit({
    queueKey: 'data-enrichment',
    data: enrichedData,
    summary: 'Verify Acme Corp enrichment before CRM update',
});

// Wait for reviewer approval
const result = await datashift.task.waitForReview(task.id, {
    timeout: 300000,    // 5 minutes
    pollInterval: 5000, // Check every 5 seconds
});

// Apply verified changes to CRM
if (result.result.includes('approved')) {
    await updateCRM(enrichedData.companyId, enrichedData.enriched);
}

Authentication

Create an API key in the Datashift Console.

const datashift = new DatashiftRestClient({
    apiKey: 'sk_live_...'
});

REST Client API

Task Operations

// Submit a task
const task = await datashift.task.submit({
    queueKey: 'approvals',
    data: {...},
    summary: 'Short description',
});

// Get task by ID
const task = await datashift.task.get(taskId);

// List tasks
const tasks = await datashift.task.list({
    queueId?: string;
    state?: 'pending' | 'queued' | 'reviewed';
});

// Wait for review
const reviewed = await datashift.task.waitForReview(taskId, {
    timeout: 300000,       // 5 minutes
    pollInterval: 2000,    // Start at 2s
    maxPollInterval: 30000 // Back off to 30s
});

Queue Operations

// List queues
const queues = await datashift.queue.list();

// Get queue config
const queue = await datashift.queue.get('refund-approvals');

Review Operations

// List reviews with filters
const reviews = await datashift.review.list({
    queueKey: 'approvals',
    reviewerType: 'human',
    from: new Date('2024-01-01'),
    limit: 50,
});

// Get review by ID
const review = await datashift.review.get(reviewId);

Webhook Operations

// Create webhook
const webhook = await datashift.webhook.create({
    url: 'https://your-service.com/webhook',
    events: ['task.reviewed'],
});
// Save webhook.secret securely!

// List webhooks
const webhooks = await datashift.webhook.list();

// Update webhook
const updated = await datashift.webhook.update(webhookId, {
    url: 'https://new-url.com/webhook',
    events: ['task.reviewed', 'task.created'],
    active: true,
});

// Rotate webhook secret
const rotated = await datashift.webhook.rotateSecret(webhookId);
// Save rotated.secret securely!

// Send test event
await datashift.webhook.test(webhookId);

// Delete webhook
await datashift.webhook.delete(webhookId);

Webhooks

Webhooks provide async notifications when tasks are reviewed.

Setting Up a Webhook Endpoint

import express from 'express';
import {verifyWebhookSignature, parseWebhookEvent} from '@datashift/sdk';
import type {WebhookEvent} from '@datashift/sdk';

const app = express();

// IMPORTANT: Use raw body for signature verification
app.post('/webhook/datashift',
    express.raw({type: 'application/json'}),
    (req, res) => {
        const signature = req.headers['x-datashift-signature'] as string;

        // Verify signature
        if (!verifyWebhookSignature(req.body, signature, WEBHOOK_SECRET)) {
            return res.status(401).send('Invalid signature');
        }

        // Parse event
        const event = parseWebhookEvent<WebhookEvent>(req.body);

        if (event.event === 'task.reviewed') {
            const {task, queue, reviews} = event.data;
            console.log(`Task ${task.id} in queue ${queue.key} reviewed`);
            console.log(`Result: ${reviews[0].result}`);
            console.log(`Reviewer: ${reviews[0].reviewer.name}`);
        } else if (event.event === 'task.created') {
            const {task, queue} = event.data;
            console.log(`Task ${task.id} created in queue ${queue.key}`);
        }

        res.status(200).send('OK');
    }
);

Webhook Events

Event Description
task.created A new task was submitted for review
task.reviewed A task has been reviewed (includes all reviews)

Webhook Payloads

// task.created
interface TaskCreatedWebhookEvent {
    event: 'task.created';
    timestamp: string;
    data: {
        task: WebhookTaskData;
        queue: WebhookQueueData;
    };
}

// task.reviewed
interface TaskReviewedWebhookEvent {
    event: 'task.reviewed';
    timestamp: string;
    data: {
        task: WebhookTaskData;
        queue: WebhookQueueData;
        reviews: WebhookReviewData[];  // All reviews (supports two-step workflows)
    };
}

interface WebhookTaskData {
    id: string;
    external_id: string | null;
    state: string;
    summary: string | null;
    data: Record<string, unknown>;
    metadata: Record<string, unknown>;
    sla_deadline: string | null;
    reviewed_at: string | null;
    created_at: string;
}

interface WebhookQueueData {
    key: string;
    name: string;
    review_type: string;
}

interface WebhookReviewData {
    result: string[];
    data: Record<string, unknown>;
    comment: string | null;
    reviewer: { name: string; type: string };
    created_at: string;
}

// Union type for all webhook events
type WebhookEvent = TaskCreatedWebhookEvent | TaskReviewedWebhookEvent;

Types

Task

interface Task {
    id: string;
    queue_id: string;
    external_id: string | null;
    state: 'pending' | 'queued' | 'reviewed';
    data: Record<string, unknown>;
    context: Record<string, unknown>;
    metadata: Record<string, unknown>;
    summary: string | null;
    sla_deadline: string | null;
    reviewed_at: string | null;
    created_at: string;
    updated_at: string;
    reviews?: Review[];
}

Queue

interface Queue {
    id: string;
    key: string;
    name: string;
    description: string | null;
    review_type: 'approval' | 'labeling' | 'classification' | 'scoring' | 'augmentation';
    review_options: ReviewOption[];
    assignment: 'manual' | 'round_robin' | 'ai_first';
    sla_minutes: number | null;
    deleted_at: string | null;
}

Review

interface Review {
    id: string;
    task_id: string;
    reviewer_id: string | null;
    result: string[];
    data: Record<string, unknown>;
    comment: string | null;
    created_at: string;
}

ListReviewsFilters

interface ListReviewsFilters {
    taskId?: string;
    reviewerId?: string;
    queueKey?: string;
    reviewerType?: 'ai' | 'human';
    from?: Date | string;
    to?: Date | string;
    limit?: number;
    offset?: number;
}

ReviewResult

interface ReviewResult {
    task_id: string;
    result: string[];
    data: Record<string, unknown>;
    reviewed_at: string;
    review: Review;
}

Error Handling

The SDK provides typed error classes for common error scenarios.

import {
    DatashiftError,
    AuthenticationError,
    NotFoundError,
    ValidationError,
    TimeoutError,
    RateLimitError,
    ServerError,
} from '@datashift/sdk';

try {
    const result = await datashift.task.waitForReview(taskId, {timeout: 60000});
} catch (error) {
    if (error instanceof TimeoutError) {
        console.log('Review not finished in time');
    } else if (error instanceof AuthenticationError) {
        console.log('Invalid credentials');
    } else if (error instanceof NotFoundError) {
        console.log('Task not found');
    } else if (error instanceof RateLimitError) {
        console.log(`Rate limited. Retry after ${error.retryAfter}s`);
    } else if (error instanceof DatashiftError) {
        console.log(`API error: ${error.message}`);
    }
}

Configuration

REST Client Options

interface RestClientConfig {
    apiKey: string;       // Required
    baseUrl?: string;     // Default: 'https://api.datashift.io'
    timeout?: number;     // Request timeout (default: 30000ms)
    retries?: number;     // Retry count (default: 3)
    retryDelay?: number;  // Initial retry delay (default: 1000ms)
}

Wait Options

interface WaitOptions {
    timeout?: number;        // Max wait time (default: 300000ms)
    pollInterval?: number;   // Poll interval (default: 2000ms)
    maxPollInterval?: number; // Max poll interval for backoff (default: 30000ms)
}

Examples

Backend Service with Webhooks

import {DatashiftRestClient, verifyWebhookSignature, parseWebhookEvent} from '@datashift/sdk';
import type {WebhookEvent} from '@datashift/sdk';
import express from 'express';

const datashift = new DatashiftRestClient({
    apiKey: process.env.DATASHIFT_API_KEY!,
});

// Store pending tasks (use Redis/DB in production)
const pendingTasks = new Map<string, (result: any) => void>();

// Submit task and return immediately
async function submitForReview(data: any): Promise<string> {
    const task = await datashift.task.submit({
        queueKey: 'reviews',
        data,
    });
    return task.id;
}

// Webhook handler
const app = express();

app.post('/webhook/datashift',
    express.raw({type: 'application/json'}),
    (req, res) => {
        const signature = req.headers['x-datashift-signature'] as string;

        if (!verifyWebhookSignature(req.body, signature, process.env.WEBHOOK_SECRET!)) {
            return res.status(401).send('Invalid signature');
        }

        const event = parseWebhookEvent<WebhookEvent>(req.body);

        if (event.event === 'task.reviewed') {
            const resolver = pendingTasks.get(event.data.task.id);
            if (resolver) {
                resolver(event.data);
                pendingTasks.delete(event.data.task.id);
            }
        }

        res.status(200).send('OK');
    }
);

app.listen(3000);

Requirements

  • Node.js 18+
  • TypeScript 5.0+ (for type definitions)

Resources

License

MIT

About

Official TypeScript SDK for Datashift — human-in-the-loop review workflows for AI agents

Topics

Resources

License

Contributing

Security policy

Stars

Watchers

Forks

Packages

 
 
 

Contributors