Skip to main content

Bulk Operations

Create, update, or delete multiple links in a single API request for efficient batch processing.

Prerequisites

Templates must be created before bulk creating links. Each link requires a valid templateId that references an existing template.

See Link Templates for details on creating templates.

Overview

Bulk operations allow you to process up to 100 links per request, significantly reducing API calls and improving performance for large-scale link management.

Available Operations:

  • Bulk Create - Create multiple links at once
  • Bulk Update - Update multiple links simultaneously
  • Bulk Delete - Delete multiple links in one request

Bulk Create

Create up to 100 links in a single request.

Endpoint

POST /api/links/bulk-create

Authentication

Requires authentication via:

  • Bearer token (user session)
  • API key in Authorization header

Request Body

FieldTypeRequiredDescription
linksarrayYesArray of link objects (max 100)

Each link object follows the same schema as Create Link.

Response

{
"created": 25,
"links": [
{
"id": "link_1",
"shortCode": "abc123",
"originalUrl": "https://example.com/product/1",
"title": "Product 1"
},
// ... 24 more links
]
}

Example Request

curl -X POST https://api.linkforty.com/api/links/bulk-create \
-H "Authorization: Bearer $LINKFORTY_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"links": [
{
"templateId": "tmpl_123",
"originalUrl": "https://example.com/product/headphones",
"title": "Wireless Headphones",
"utmParameters": {
"source": "instagram",
"medium": "social",
"campaign": "spring-sale"
}
},
{
"templateId": "tmpl_123",
"originalUrl": "https://example.com/product/speakers",
"title": "Bluetooth Speakers",
"utmParameters": {
"source": "instagram",
"medium": "social",
"campaign": "spring-sale"
}
}
]
}'

Use Cases

1. Import from CSV

import { parse } from 'csv-parse/sync';

async function importLinksFromCSV(csvContent: string, templateId: string) {
const records = parse(csvContent, { columns: true });

const links = records.map(row => ({
templateId,
originalUrl: row.url,
title: row.title,
description: row.description,
utmParameters: {
source: row.utm_source,
medium: row.utm_medium,
campaign: row.utm_campaign,
},
}));

// Process in batches of 100
const batches = [];
for (let i = 0; i < links.length; i += 100) {
batches.push(links.slice(i, i + 100));
}

for (const batch of batches) {
const result = await bulkCreateLinks(batch);
console.log(`Created ${result.created} links`);
}
}
async function createProductLinks(products: Product[], campaign: string) {
const links = products.map(product => ({
templateId: 'product_template',
originalUrl: `https://shop.example.com/products/${product.id}`,
title: product.name,
description: product.description,
utmParameters: {
source: 'catalog',
medium: 'website',
campaign,
content: product.category,
},
}));

return bulkCreateLinks(links);
}

Bulk Update

Update up to 100 links in a single request.

Endpoint

POST /api/links/bulk-update

Request Body

FieldTypeRequiredDescription
updatesarrayYesArray of update objects (max 100)

Each update object contains:

FieldTypeRequiredDescription
idstring (UUID)YesLink ID to update
dataobjectYesFields to update (same as Update Link)

Response

{
"updated": 15,
"links": [
{
"id": "link_1",
"shortCode": "abc123",
"title": "Updated Title",
"updatedAt": "2024-03-15T10:30:00Z"
},
// ... 14 more links
]
}

Example Request

curl -X POST https://api.linkforty.com/api/links/bulk-update \
-H "Authorization: Bearer $LINKFORTY_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"updates": [
{
"id": "link_1",
"data": {
"utmParameters": {
"campaign": "summer-sale-2024"
}
}
},
{
"id": "link_2",
"data": {
"utmParameters": {
"campaign": "summer-sale-2024"
}
}
}
]
}'

Use Cases

async function updateCampaign(linkIds: string[], newCampaign: string) {
const updates = linkIds.map(id => ({
id,
data: {
utmParameters: {
campaign: newCampaign,
},
},
}));

// Process in batches of 100
for (let i = 0; i < updates.length; i += 100) {
const batch = updates.slice(i, i + 100);
await bulkUpdateLinks(batch);
}
}

2. Extend Attribution Window

async function extendAttributionWindow(linkIds: string[], hours: number) {
const updates = linkIds.map(id => ({
id,
data: { attributionWindowHours: hours },
}));

return bulkUpdateLinks(updates);
}
async function deactivateLinks(linkIds: string[]) {
const updates = linkIds.map(id => ({
id,
data: { isActive: false },
}));

return bulkUpdateLinks(updates);
}
async function updateFromSpreadsheet(updates: Array<{
shortCode: string;
newUrl: string;
newTitle: string;
}>) {
// First, get all links to map short codes to IDs
const allLinks = await getAllLinks();
const linkMap = new Map(allLinks.map(l => [l.shortCode, l.id]));

// Build update requests
const updateRequests = updates
.map(update => {
const linkId = linkMap.get(update.shortCode);
if (!linkId) return null;

return {
id: linkId,
data: {
originalUrl: update.newUrl,
title: update.newTitle,
},
};
})
.filter(Boolean);

return bulkUpdateLinks(updateRequests);
}

Bulk Delete

Delete up to 100 links in a single request.

Endpoint

POST /api/links/bulk-delete

Request Body

FieldTypeRequiredDescription
idsstring[]YesArray of link IDs (max 100)

Response

{
"deleted": 42
}

Example Request

curl -X POST https://api.linkforty.com/api/links/bulk-delete \
-H "Authorization: Bearer $LINKFORTY_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"ids": [
"link_1",
"link_2",
"link_3"
]
}'

Use Cases

async function deleteTestLinks() {
const links = await getAllLinks();

const testLinkIds = links
.filter(link => link.title?.toLowerCase().includes('test'))
.map(link => link.id);

if (testLinkIds.length === 0) {
console.log('No test links found');
return;
}

// Delete in batches of 100
for (let i = 0; i < testLinkIds.length; i += 100) {
const batch = testLinkIds.slice(i, i + 100);
const result = await bulkDeleteLinks(batch);
console.log(`Deleted ${result.deleted} links`);
}
}
async function deleteExpiredLinks() {
const links = await getAllLinks();
const now = new Date();

const expiredLinkIds = links
.filter(link => {
if (!link.expiresAt) return false;
return new Date(link.expiresAt) < now;
})
.map(link => link.id);

if (expiredLinkIds.length > 0) {
return bulkDeleteLinks(expiredLinkIds);
}
}
async function deleteUnusedLinks(minAge: number = 30) {
const links = await getAllLinks();
const cutoffDate = new Date();
cutoffDate.setDate(cutoffDate.getDate() - minAge);

const unusedLinkIds = links
.filter(link => {
const created = new Date(link.createdAt);
return created < cutoffDate && link.clickCount === 0;
})
.map(link => link.id);

console.log(`Found ${unusedLinkIds.length} unused links`);

if (unusedLinkIds.length > 0) {
return bulkDeleteLinks(unusedLinkIds);
}
}

TypeScript Interface

interface BulkCreateRequest {
links: CreateLinkRequest[];
}

interface BulkCreateResponse {
created: number;
links: Link[];
}

interface BulkUpdateRequest {
updates: Array<{
id: string;
data: UpdateLinkRequest;
}>;
}

interface BulkUpdateResponse {
updated: number;
links: Link[];
}

interface BulkDeleteRequest {
ids: string[];
}

interface BulkDeleteResponse {
deleted: number;
}

// Helper functions
async function bulkCreateLinks(links: CreateLinkRequest[]): Promise<BulkCreateResponse> {
const response = await fetch('https://api.linkforty.com/api/links/bulk-create', {
method: 'POST',
headers: {
'Authorization': `Bearer ${API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({ links }),
});

return response.json();
}

async function bulkUpdateLinks(updates: Array<{ id: string; data: UpdateLinkRequest }>): Promise<BulkUpdateResponse> {
const response = await fetch('https://api.linkforty.com/api/links/bulk-update', {
method: 'POST',
headers: {
'Authorization': `Bearer ${API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({ updates }),
});

return response.json();
}

async function bulkDeleteLinks(ids: string[]): Promise<BulkDeleteResponse> {
const response = await fetch('https://api.linkforty.com/api/links/bulk-delete', {
method: 'POST',
headers: {
'Authorization': `Bearer ${API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({ ids }),
});

return response.json();
}

Error Responses

Invalid Array

{
"statusCode": 400,
"error": "Bad Request",
"message": "Invalid links array"
}

Causes:

  • Missing links field (bulk create)
  • Missing updates field (bulk update)
  • Missing ids field (bulk delete)
  • Not an array

Too Many Items

{
"statusCode": 400,
"error": "Bad Request",
"message": "Maximum 100 links allowed per bulk create"
}

Cause: More than 100 items in request.

Validation Error

{
"statusCode": 400,
"error": "Bad Request",
"message": "Validation error: Unable to generate unique short code for link at index 42"
}

Cause: One or more links in the batch failed validation.

{
"statusCode": 403,
"error": "Forbidden",
"message": "Link limit exceeded for your organization"
}

Cause: Bulk create would exceed your plan's link limit.

Best Practices

1. Process in Batches of 100

async function processBatches<T>(items: T[], batchSize: number = 100) {
const results = [];

for (let i = 0; i < items.length; i += batchSize) {
const batch = items.slice(i, i + batchSize);
results.push(await processBatch(batch));
console.log(`Processed ${Math.min(i + batchSize, items.length)}/${items.length}`);
}

return results;
}

2. Handle Partial Failures

async function robustBulkCreate(links: CreateLinkRequest[]) {
const results = {
succeeded: [] as Link[],
failed: [] as { link: CreateLinkRequest; error: string }[],
};

// Process in batches
for (let i = 0; i < links.length; i += 100) {
const batch = links.slice(i, i + 100);

try {
const response = await bulkCreateLinks(batch);
results.succeeded.push(...response.links);
} catch (error) {
// If batch fails, try individually
for (const link of batch) {
try {
const created = await createLink(link);
results.succeeded.push(created);
} catch (err) {
results.failed.push({ link, error: err.message });
}
}
}
}

return results;
}

3. Validate Before Sending

function validateBulkCreate(links: CreateLinkRequest[]): string[] {
const errors = [];

if (links.length > 100) {
errors.push('Maximum 100 links per request');
}

links.forEach((link, index) => {
if (!link.originalUrl) {
errors.push(`Link ${index}: Missing originalUrl`);
}
if (!link.templateId) {
errors.push(`Link ${index}: Missing templateId`);
}
});

return errors;
}

// Usage
const errors = validateBulkCreate(links);
if (errors.length > 0) {
console.error('Validation errors:', errors);
} else {
await bulkCreateLinks(links);
}

4. Show Progress

async function bulkCreateWithProgress(links: CreateLinkRequest[]) {
const total = links.length;
let created = 0;

for (let i = 0; i < links.length; i += 100) {
const batch = links.slice(i, i + 100);
const result = await bulkCreateLinks(batch);

created += result.created;
const progress = ((created / total) * 100).toFixed(1);

console.log(`Progress: ${created}/${total} (${progress}%)`);
updateProgressBar(progress);
}

console.log('Complete!');
}

5. Retry on Failure

async function bulkCreateWithRetry(
links: CreateLinkRequest[],
maxRetries: number = 3
) {
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
return await bulkCreateLinks(links);
} catch (error) {
if (attempt === maxRetries) throw error;

console.log(`Attempt ${attempt} failed, retrying...`);
await new Promise(resolve => setTimeout(resolve, 1000 * attempt));
}
}
}

Limitations

LimitValue
Max items per request100
Request size~1 MB
Rate limitStandard rate limits apply

Performance Tips

  1. Use bulk operations instead of loops

    • ✅ 100 links in 1 request = 1 API call
    • ❌ 100 links in loop = 100 API calls
  2. Batch your operations

    • Process 100 at a time
    • Show progress to user
    • Handle failures gracefully
  3. Validate client-side first

    • Check URLs are valid
    • Ensure required fields present
    • Reduce failed requests
  4. Use compression

    • Enable gzip for large payloads
    • Reduces transfer time

Guides