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);
}

Import 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. Handles 402 payments individually per entry
  5. Provides detailed success/failure tracking
  6. Stores content securely per entry
  7. 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('402')) {
    console.error('Payment failed for batch');
  } 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
  • Payment: Individual x402 payments per entry (not batched)
  • 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