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.
Load large batches of issues from CSV files, JSON feeds, or external APIs — practical patterns for populating a real work queue.
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.
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;
# 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
// 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}`);
}
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;
\COPY and an INSERT/SELECTmetadata for idempotent external-ID seeding