JSPM

  • ESM via JSPM
  • ES Module Entrypoint
  • Export Map
  • Keywords
  • License
  • Repository URL
  • TypeScript Types
  • README
  • Created
  • Published
  • Downloads 58
  • Score
    100M100P100Q84995F
  • License MIT

Datashift SDK for human-in-the-loop AI agent workflows

Package Exports

  • @datashift/sdk

Readme

@datashift/sdk

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.

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_id, result, review} = event.data;
            handleTaskReviewed(task_id, result, review);
        }

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

Webhook Events

Event Description
task.reviewed Task has been reviewed

Webhook Payload

interface WebhookEvent {
    event: string;      // Event type
    timestamp: string;  // ISO 8601 timestamp
    data: {
        task_id: string;
        queue_key: string;
        state: string;
        result: string[];
        review?: Review;
    };
}

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)

License

MIT