Bulk Issue Seeding & Import

The REST endpoint for batch-creating issues, CSV import format, pre-populating a company's backlog via curl/Postman, and idempotency patterns for re-runnable seeds.

Bulk-Seeding Issues from External Data

What you'll learn

Load large batches of issues from CSV files, JSON feeds, or external APIs — practical patterns for populating a real work queue.

Why Bulk Seeding

When you connect Paperclip to a real data source (a support ticket system, a content backlog, a code review queue), you need to import tens or hundreds of issues at a time, not create them one by one.

Loading from a CSV

Given tasks.csv:

title,description,priority
"Review PR #42","Check for security issues in auth module","high"
"Write test for payment flow","Unit tests for checkout","medium"
"Update docs: API endpoints","Reflect new v2 routes","low"
-- Create a staging table
CREATE TEMP TABLE task_import (
  title       text,
  description text,
  priority    text
);

-- Load CSV
\COPY task_import FROM '/tmp/tasks.csv' CSV HEADER;

-- Insert into issues, assigned to the right agent
INSERT INTO issues (company_id, title, description, priority, assigned_agent_id, status)
SELECT
  'your-company-id',
  ti.title,
  ti.description,
  ti.priority,
  (SELECT id FROM agents WHERE role = 'Backend Developer' AND company_id = 'your-company-id' LIMIT 1),
  'open'
FROM task_import ti
ON CONFLICT DO NOTHING;

Loading from a JSON Array

# tasks.json: [{"title":"...", "priority":"high"}, ...]
psql $DATABASE_URL << 'SQL'
INSERT INTO issues (company_id, title, priority, assigned_agent_id, status)
SELECT
  'your-company-id',
  (item->>'title')::text,
  COALESCE((item->>'priority')::text, 'medium'),
  (SELECT id FROM agents WHERE role = 'Researcher' LIMIT 1),
  'open'
FROM jsonb_array_elements(pg_read_file('/tmp/tasks.json')::jsonb) AS item
ON CONFLICT DO NOTHING;
SQL

Loading from the API with a Script

// scripts/seed-issues.ts
import fs from 'fs';

const COMPANY_ID = process.env.COMPANY_ID!;
const AGENT_ID   = process.env.AGENT_ID!;
const API_BASE   = 'http://localhost:3100';

const tasks = JSON.parse(fs.readFileSync('tasks.json', 'utf8'));

for (const task of tasks) {
  const res = await fetch(`${API_BASE}/api/companies/${COMPANY_ID}/issues`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      title:             task.title,
      description:       task.description,
      priority:          task.priority ?? 'medium',
      assigned_agent_id: AGENT_ID,
    }),
  });
  const data = await res.json();
  console.log(`Created: ${data.id} — ${task.title}`);
}

Avoiding Duplicate Seeds

Use idempotent seeding with a unique key from your source:

-- Store external ID in metadata
INSERT INTO issues (company_id, title, metadata, status)
VALUES ('cid', 'Task from Jira PROJ-42', '{"jira_id": "PROJ-42"}', 'open')
ON CONFLICT ((metadata->>'jira_id')) DO NOTHING;

-- Requires a partial unique index:
CREATE UNIQUE INDEX idx_issues_jira_id
ON issues ((metadata->>'jira_id'))
WHERE metadata->>'jira_id' IS NOT NULL;

Checkpoint ✓

Sign in for progress tracking & Q&A →