Skip to content

entries.batchCreate()

Efficiently create multiple entries in a single operation.

Signature

entries.batchCreate(
  feedId: string, 
  entries: CreateEntryInput[], 
  options?: {
    onProgress?: (completed: number, total: number) => void;
    delayMs?: number;
  }
): Promise<{ successful: Entry[]; failed: { input: CreateEntryInput; error: string }[] }>

Parameters

feedId

  • Type: string
  • Required: Yes
  • Description: The UUID of the feed to add entries to

entries

  • Type: CreateEntryInput[]
  • Required: Yes
  • Description: Array of entry objects to create
interface CreateEntryInput {
  content: string | Buffer | object;  // Content data
  mime_type?: string;                  // Auto-detected if not provided
  title?: string;                      // Entry title
  description?: string;                // Entry description
  metadata?: Record<string, any>;      // Additional metadata
  tags?: string[];                     // Entry tags
  is_free?: boolean;                   // Free access (default: true)
  expires_at?: number;                 // Unix timestamp expiration
  price?: {                           // Price for paid entries
    amount: string;
    currency: string;
  };
}

Returns

  • Type: Promise<{ successful: Entry[]; failed: { input: CreateEntryInput; error: string }[] }>

Returns an object containing:

  • successful: Array of successfully created entries
  • failed: Array of failed entries with error details

Usage

Basic Example

const result = await grapevine.entries.batchCreate(feedId, [
  {
    title: 'Entry 1',
    content: 'First entry content',
    is_free: true
  },
  {
    title: 'Entry 2', 
    content: 'Second entry content',
    tags: ['update'],
    description: 'An important update'
  },
  {
    title: 'Entry 3',
    content: { type: 'premium', data: 'exclusive content' },
    tags: ['premium'],
    is_free: false,
    price: { amount: '1000000', currency: 'USDC' }
  }
]);
 
console.log(`Created ${result.successful.length} entries`);
console.log(`Failed ${result.failed.length} entries`);
 
// Handle failures
if (result.failed.length > 0) {
  console.error('Failed entries:', result.failed);
}

Batch Upload Files from Directory

import { readFileSync, readdirSync } from 'fs';
import path from 'path';
 
// Upload all PDFs from a directory
async function uploadPDFDirectory(feedId: string, dirPath: string) {
  const files = readdirSync(dirPath).filter(f => f.endsWith('.pdf'));
  
  const entries = files.map(filename => ({
    title: filename.replace('.pdf', ''),
    content: readFileSync(path.join(dirPath, filename)),
    mime_type: 'application/pdf',
    tags: ['document', 'pdf']
  }));
  
  const result = await grapevine.entries.batchCreate(feedId, entries, {
    onProgress: (completed, total) => {
      console.log(`Uploaded ${completed}/${total} files`);
    },
    delayMs: 1000
  });
  
  console.log(`Successfully uploaded ${result.successful.length} PDFs`);
  return result;
}
 
// Usage
await uploadPDFDirectory(feedId, './reports');

Batch Upload Images

import { readFileSync, readdirSync } from 'fs';
import path from 'path';
import mime from 'mime-types';
 
// Upload all images from a directory
async function uploadImages(feedId: string, dirPath: string) {
  const imageExtensions = ['.png', '.jpg', '.jpeg', '.gif', '.webp'];
  const files = readdirSync(dirPath).filter(f => 
    imageExtensions.some(ext => f.toLowerCase().endsWith(ext))
  );
  
  const entries = files.map(filename => ({
    title: filename,
    content: readFileSync(path.join(dirPath, filename)),
    mime_type: mime.lookup(filename) || 'application/octet-stream',
    tags: ['image', 'gallery']
  }));
  
  return grapevine.entries.batchCreate(feedId, entries, {
    onProgress: (completed, total) => {
      console.log(`Progress: ${completed}/${total}`);
    },
    delayMs: 500
  });
}

Import Articles from Data Source

// Import articles from external source with progress tracking
async function importArticles(feedId: string, articles: Article[]) {
  const entries = articles.map(article => ({
    title: article.title,
    content: article.body,
    description: article.summary,
    tags: article.tags,
    is_free: article.category === 'preview',
    mime_type: 'text/markdown',
    metadata: {
      originalUrl: article.originalUrl,
      author: article.author,
      publishDate: article.publishDate
    }
  }));
  
  const result = await grapevine.entries.batchCreate(feedId, entries, {
    onProgress: (completed, total) => {
      console.log(`Progress: ${completed}/${total} (${Math.round(completed/total*100)}%)`);
    },
    delayMs: 1000 // 1 second delay between requests to avoid rate limiting
  });
  
  console.log(`Successfully imported ${result.successful.length} articles`);
  if (result.failed.length > 0) {
    console.error(`Failed to import ${result.failed.length} articles:`);
    result.failed.forEach(failure => {
      console.error(`- ${failure.input.title}: ${failure.error}`);
    });
  }
  
  return result;
}

Mixed Content Types

const mixedEntries = await grapevine.entries.batchCreate(feedId, [
  {
    title: 'Market Data',
    content: JSON.stringify({ btc: 50000, eth: 3000 }),
    mime_type: 'application/json',
    tags: ['data', 'market']
  },
  {
    title: 'Analysis',
    content: '## Market Analysis\n\nToday we see...',
    mime_type: 'text/markdown',
    tags: ['analysis']
  },
  {
    title: 'Summary',
    content: 'Quick market summary for today',
    mime_type: 'text/plain',
    is_free: true
  }
]);

Chunked Batch Processing

// Process large dataset in chunks
async function batchImport(feedId: string, allEntries: CreateEntryInput[]) {
  const chunkSize = 10; // Process 10 at a time
  const results: Entry[] = [];
  
  for (let i = 0; i < allEntries.length; i += chunkSize) {
    const chunk = allEntries.slice(i, i + chunkSize);
    
    try {
      const result = await grapevine.entries.batchCreate(feedId, chunk);
      results.push(...result.successful);
      console.log(`Processed ${i + chunk.length}/${allEntries.length}`);
      if (result.failed.length > 0) {
        console.log(`${result.failed.length} failed in this chunk`);
      }
    } catch (error) {
      console.error(`Failed chunk ${i}-${i + chunkSize}:`, error);
    }
  }
  
  return results;
}

With Progress Tracking

async function batchCreateWithProgress(
  feedId: string, 
  entries: CreateEntryInput[],
  onProgress?: (current: number, total: number) => void
) {
  const batchSize = 5;
  const allCreated: Entry[] = [];
  
  for (let i = 0; i < entries.length; i += batchSize) {
    const batch = entries.slice(i, i + batchSize);
    const result = await grapevine.entries.batchCreate(feedId, batch);
    allCreated.push(...result.successful);
    
    if (onProgress) {
      onProgress(allCreated.length, entries.length);
    }
  }
  
  return allCreated;
}
 
// Usage
const entries = await batchCreateWithProgress(
  feedId,
  largeDataset,
  (current, total) => console.log(`Progress: ${current}/${total}`)
);

Behind the Scenes

This method:

  1. Processes entries sequentially (not truly batched)
  2. Gets authentication headers for each entry
  3. Handles rate limiting with optional delays
  4. Provides detailed success/failure tracking
  5. Stores content securely per entry
  6. Returns detailed results with error information

Error Handling

try {
  const result = await grapevine.entries.batchCreate(feedId, entriesData);
  console.log(`Successfully created ${result.successful.length} entries`);
  
  if (result.failed.length > 0) {
    console.log(`${result.failed.length} entries failed:`);
    result.failed.forEach(({ input, error }) => {
      console.log(`- "${input.title}": ${error}`);
    });
  }
} catch (error) {
  if (error.message.includes('404')) {
    console.error('Feed not found');
  } else if (error.message.includes('403')) {
    console.error('Not authorized - you can only add to your own feeds');
  } else if (error.message.includes('401')) {
    console.error('Authentication failed');
  } else if (error.message.includes('413')) {
    console.error('Batch too large - reduce number of entries');
  } else {
    console.error('Error:', error.message);
  }
}

Notes

  • Authentication: Required - must be feed owner
  • Free to Create: Creating entries does not require payment
  • Rate Limiting: Use delayMs option to control request rate
  • Partial Success: Some entries can succeed while others fail
  • Performance: Sequential processing with error handling
  • MIME Types: Auto-detected if not specified

Related