Skip to main content

Data Export

Export your analytics data for external analysis, reporting, and integration with other tools.

Overview

LinkForty's data export feature allows you to:

  • Export raw click events with complete metadata
  • Download in multiple formats (JSON, CSV)
  • Filter by date range and specific links
  • Paginate large datasets for efficient processing
  • Automate exports via API for scheduled reports

Use Cases:

  • Import into business intelligence tools (Tableau, Power BI)
  • Custom analytics and machine learning
  • Compliance and data retention
  • Backup and archival
  • Third-party integration

Export Formats

JSON Format

Structured data perfect for programmatic processing.

Advantages:

  • ✅ Preserves data types
  • ✅ Nested structures supported
  • ✅ Easy to parse with modern tools
  • ✅ Includes pagination metadata

Example:

{
"data": [
{
"id": "click_xyz789",
"linkId": "link_abc123",
"shortCode": "spring24",
"linkTitle": "Spring Sale 2024",
"originalUrl": "https://shop.example.com/sale",
"clickedAt": "2024-03-15T14:23:12Z",
"ipAddress": "192.168.1.1",
"userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 17_0...)",
"deviceType": "mobile",
"platform": "iOS",
"browser": "Safari",
"browserVersion": "17.0",
"os": "iOS",
"osVersion": "17.0",
"countryCode": "US",
"countryName": "United States",
"region": "California",
"city": "San Francisco",
"timezone": "America/Los_Angeles",
"latitude": 37.7749,
"longitude": -122.4194,
"utmSource": "instagram",
"utmMedium": "social",
"utmCampaign": "spring-sale",
"utmTerm": null,
"utmContent": "story-1",
"referrer": "https://www.instagram.com/"
}
],
"pagination": {
"total": 5432,
"limit": 1000,
"offset": 0,
"hasMore": true
}
}

CSV Format

Spreadsheet-friendly format for Excel, Google Sheets, and data analysis tools.

Advantages:

  • ✅ Opens in Excel/Sheets directly
  • ✅ Smaller file size
  • ✅ Easy to share with non-technical users
  • ✅ Compatible with most tools

Example:

id,linkId,shortCode,clickedAt,deviceType,platform,countryCode,city,utmSource,utmCampaign
click_1,link_abc123,spring24,2024-03-15T14:23:12Z,mobile,iOS,US,San Francisco,instagram,spring-sale
click_2,link_abc123,spring24,2024-03-15T14:25:03Z,desktop,Windows,CA,Toronto,email,spring-sale
click_3,link_def456,newprod,2024-03-15T14:27:18Z,mobile,Android,GB,London,twitter,product-launch

Exporting Data

Via Dashboard

  1. Go to AnalyticsOverview
  2. Click "Export Data" button
  3. Configure export:
    • Date Range: Select start and end dates
    • Link Filter: (Optional) Filter by specific link
    • Format: Choose JSON or CSV
    • Limit: Max records per export
  4. Click "Export"
  5. Download file

Via API

curl "https://api.linkforty.com/api/analytics/export?startDate=2024-03-01&endDate=2024-03-31&format=csv&limit=1000" \
-H "Authorization: Bearer $LINKFORTY_API_KEY"

Query Parameters:

ParameterTypeRequiredDescriptionDefault
startDatestring (YYYY-MM-DD)NoExport start date30 days ago
endDatestring (YYYY-MM-DD)NoExport end dateToday
linkIdstring (UUID)NoFilter by specific linkAll links
formatjson | csvNoExport formatjson
limitnumberNoMax records (1-10000)1000
offsetnumberNoPagination offset0

Requires: Member, Admin, or Owner role

Export Fields

All exports include the following fields:

Click Event Data

FieldTypeDescription
idstringUnique click event ID
linkIdstringAssociated link ID
shortCodestringLink's short code
linkTitlestringLink title
originalUrlstringDestination URL
clickedAttimestampWhen click occurred (UTC)

User Information

FieldTypeDescription
ipAddressstringUser's IP address
userAgentstringFull user-agent string

Device & Platform

FieldTypeDescription
deviceTypestringmobile, desktop, or tablet
platformstringiOS, Android, Windows, macOS, Linux
browserstringChrome, Safari, Firefox, Edge, etc.
browserVersionstringBrowser version number
osstringOperating system
osVersionstringOS version number

Geographic Data

FieldTypeDescription
countryCodestringISO 3166-1 alpha-2 country code
countryNamestringFull country name
regionstringState/province
citystringCity name
timezonestringIANA timezone identifier
latitudenumberGeographic latitude
longitudenumberGeographic longitude

Campaign Tracking

FieldTypeDescription
utmSourcestringCampaign source
utmMediumstringCampaign medium
utmCampaignstringCampaign name
utmTermstringCampaign term (optional)
utmContentstringCampaign content (optional)
referrerstringHTTP referrer URL

Pagination

For large datasets, use pagination to retrieve data in chunks.

Example: Paginated Export

async function exportAllData(
startDate: string,
endDate: string
): Promise<any[]> {
const allData = [];
let offset = 0;
const limit = 1000;
let hasMore = true;

while (hasMore) {
const response = await fetch(
`https://api.linkforty.com/api/analytics/export?` +
`startDate=${startDate}&endDate=${endDate}&` +
`format=json&limit=${limit}&offset=${offset}`,
{ headers: { 'Authorization': `Bearer ${API_KEY}` } }
);

const result = await response.json();
allData.push(...result.data);

hasMore = result.pagination.hasMore;
offset += limit;

console.log(`Fetched ${allData.length} of ${result.pagination.total} records`);

// Rate limit: wait 100ms between requests
await new Promise(resolve => setTimeout(resolve, 100));
}

return allData;
}

// Usage
const allClicks = await exportAllData('2024-03-01', '2024-03-31');
console.log(`✅ Exported ${allClicks.length} total clicks`);

Common Export Scenarios

1. Monthly Report Export

async function exportMonthlyReport(year: number, month: number) {
// Calculate date range
const startDate = new Date(year, month - 1, 1)
.toISOString()
.split('T')[0];

const endDate = new Date(year, month, 0)
.toISOString()
.split('T')[0];

// Export as CSV
const response = await fetch(
`https://api.linkforty.com/api/analytics/export?` +
`startDate=${startDate}&endDate=${endDate}&format=csv&limit=10000`,
{ headers: { 'Authorization': `Bearer ${API_KEY}` } }
);

const csv = await response.text();

// Save to file
const filename = `analytics-${year}-${month.toString().padStart(2, '0')}.csv`;
await fs.writeFile(filename, csv);

console.log(`✅ Exported to ${filename}`);
}

// Export March 2024
await exportMonthlyReport(2024, 3);
async function exportLinkData(linkId: string, days: number = 30) {
const endDate = new Date().toISOString().split('T')[0];
const startDate = new Date(Date.now() - days * 24 * 60 * 60 * 1000)
.toISOString()
.split('T')[0];

const response = await fetch(
`https://api.linkforty.com/api/analytics/export?` +
`startDate=${startDate}&endDate=${endDate}&` +
`linkId=${linkId}&format=json&limit=10000`,
{ headers: { 'Authorization': `Bearer ${API_KEY}` } }
);

const result = await response.json();

return {
link: linkId,
clicks: result.data,
total: result.pagination.total,
};
}

// Export specific link
const linkData = await exportLinkData('link_abc123', 30);
console.log(`Exported ${linkData.total} clicks for link ${linkData.link}`);

3. Automated Daily Backup

async function dailyBackup() {
const today = new Date().toISOString().split('T')[0];
const yesterday = new Date(Date.now() - 24 * 60 * 60 * 1000)
.toISOString()
.split('T')[0];

const response = await fetch(
`https://api.linkforty.com/api/analytics/export?` +
`startDate=${yesterday}&endDate=${today}&format=json&limit=10000`,
{ headers: { 'Authorization': `Bearer ${API_KEY}` } }
);

const result = await response.json();

// Save to backup storage
const filename = `backup-${yesterday}.json`;
await fs.writeFile(filename, JSON.stringify(result, null, 2));

console.log(`✅ Backed up ${result.pagination.total} clicks to ${filename}`);
}

// Run daily via cron
// 0 1 * * * node daily-backup.js

4. Campaign Performance Analysis

async function analyzeCampaignPerformance(
campaign: string,
startDate: string,
endDate: string
) {
const allData = await exportAllData(startDate, endDate);

// Filter by campaign
const campaignClicks = allData.filter(
click => click.utmCampaign === campaign
);

// Analyze
const analysis = {
campaign,
totalClicks: campaignClicks.length,
uniqueIPs: new Set(campaignClicks.map(c => c.ipAddress)).size,
topCountries: getTopN(campaignClicks, 'countryName', 5),
topCities: getTopN(campaignClicks, 'city', 5),
deviceBreakdown: getBreakdown(campaignClicks, 'deviceType'),
platformBreakdown: getBreakdown(campaignClicks, 'platform'),
hourlyDistribution: getHourlyDistribution(campaignClicks),
};

return analysis;
}

function getTopN(data: any[], field: string, n: number) {
const counts = data.reduce((acc, item) => {
const value = item[field] || 'Unknown';
acc[value] = (acc[value] || 0) + 1;
return acc;
}, {} as Record<string, number>);

return Object.entries(counts)
.sort((a, b) => b[1] - a[1])
.slice(0, n)
.map(([name, clicks]) => ({ name, clicks }));
}

function getBreakdown(data: any[], field: string) {
const counts = data.reduce((acc, item) => {
const value = item[field] || 'Unknown';
acc[value] = (acc[value] || 0) + 1;
return acc;
}, {} as Record<string, number>);

return Object.entries(counts).map(([type, clicks]) => ({ type, clicks }));
}

function getHourlyDistribution(data: any[]) {
const hourCounts = data.reduce((acc, click) => {
const hour = new Date(click.clickedAt).getHours();
acc[hour] = (acc[hour] || 0) + 1;
return acc;
}, {} as Record<number, number>);

return Object.entries(hourCounts)
.map(([hour, clicks]) => ({ hour: parseInt(hour), clicks }))
.sort((a, b) => a.hour - b.hour);
}

// Usage
const analysis = await analyzeCampaignPerformance(
'spring-sale',
'2024-03-01',
'2024-03-31'
);

console.log(JSON.stringify(analysis, null, 2));

5. Export to Google Sheets

import { google } from 'googleapis';

async function exportToGoogleSheets(
startDate: string,
endDate: string,
spreadsheetId: string
) {
// Fetch data
const response = await fetch(
`https://api.linkforty.com/api/analytics/export?` +
`startDate=${startDate}&endDate=${endDate}&format=json&limit=10000`,
{ headers: { 'Authorization': `Bearer ${API_KEY}` } }
);

const result = await response.json();

// Prepare for Sheets
const headers = [
'Date',
'Short Code',
'Title',
'Country',
'City',
'Device',
'Platform',
'UTM Source',
'UTM Campaign',
];

const rows = result.data.map(click => [
new Date(click.clickedAt).toLocaleString(),
click.shortCode,
click.linkTitle,
click.countryName,
click.city,
click.deviceType,
click.platform,
click.utmSource || 'Direct',
click.utmCampaign || 'None',
]);

// Authenticate with Google
const auth = new google.auth.GoogleAuth({
keyFile: 'credentials.json',
scopes: ['https://www.googleapis.com/auth/spreadsheets'],
});

const sheets = google.sheets({ version: 'v4', auth });

// Write to sheet
await sheets.spreadsheets.values.update({
spreadsheetId,
range: 'Analytics!A1',
valueInputOption: 'RAW',
requestBody: {
values: [headers, ...rows],
},
});

console.log(`✅ Exported ${rows.length} rows to Google Sheets`);
}

Data Processing Examples

Convert CSV to JSON

import * as fs from 'fs/promises';
import { parse } from 'csv-parse/sync';

async function csvToJson(csvFilePath: string) {
const csvContent = await fs.readFile(csvFilePath, 'utf-8');

const records = parse(csvContent, {
columns: true,
skip_empty_lines: true,
});

return records;
}

// Usage
const data = await csvToJson('analytics-2024-03.csv');
console.log(`Parsed ${data.length} records`);

Filter and Transform

async function filterAndTransform(
startDate: string,
endDate: string,
filters: {
country?: string;
device?: string;
campaign?: string;
}
) {
const allData = await exportAllData(startDate, endDate);

let filtered = allData;

if (filters.country) {
filtered = filtered.filter(c => c.countryCode === filters.country);
}

if (filters.device) {
filtered = filtered.filter(c => c.deviceType === filters.device);
}

if (filters.campaign) {
filtered = filtered.filter(c => c.utmCampaign === filters.campaign);
}

// Transform for external tool
return filtered.map(click => ({
timestamp: click.clickedAt,
link: click.shortCode,
location: `${click.city}, ${click.countryName}`,
device: `${click.deviceType} - ${click.platform}`,
source: click.utmSource || 'Direct',
campaign: click.utmCampaign || 'None',
}));
}

// Export US mobile clicks from Instagram
const filtered = await filterAndTransform(
'2024-03-01',
'2024-03-31',
{
country: 'US',
device: 'mobile',
campaign: 'spring-sale',
}
);

console.log(`Filtered to ${filtered.length} matching clicks`);

Generate Summary Statistics

async function generateSummaryStats(
startDate: string,
endDate: string
) {
const allData = await exportAllData(startDate, endDate);

return {
overview: {
totalClicks: allData.length,
uniqueIPs: new Set(allData.map(c => c.ipAddress)).size,
dateRange: { startDate, endDate },
},
geographic: {
countries: new Set(allData.map(c => c.countryCode)).size,
topCountry: getTopN(allData, 'countryName', 1)[0],
topCity: getTopN(allData, 'city', 1)[0],
},
devices: {
mobile: allData.filter(c => c.deviceType === 'mobile').length,
desktop: allData.filter(c => c.deviceType === 'desktop').length,
tablet: allData.filter(c => c.deviceType === 'tablet').length,
mobilePercentage: (
(allData.filter(c => c.deviceType === 'mobile').length / allData.length) *
100
).toFixed(1) + '%',
},
platforms: getBreakdown(allData, 'platform'),
campaigns: getBreakdown(allData, 'utmCampaign'),
};
}

// Usage
const stats = await generateSummaryStats('2024-03-01', '2024-03-31');
console.log(JSON.stringify(stats, null, 2));

Export Limits

By Plan

PlanMax Records per ExportMax Exports per Day
Free1,00010
Pro10,000100
EnterpriseUnlimitedUnlimited

Rate Limits

  • API Exports: Subject to standard API rate limits (see Rate Limits)
  • Concurrent Exports: 1 active export per organization
  • Recommended Delay: 100ms between paginated requests

Best Practices

1. Use Appropriate Date Ranges

✅ Good:

// Export one month at a time
await exportMonthlyReport(2024, 3);

❌ Bad:

// Trying to export entire year at once
await exportAllData('2024-01-01', '2024-12-31'); // May timeout

2. Paginate Large Exports

✅ Good:

// Fetch in chunks of 1000
let offset = 0;
while (hasMore) {
const chunk = await fetchChunk(offset, 1000);
offset += 1000;
}

❌ Bad:

// Trying to fetch 100k records at once
await fetch('...&limit=100000'); // Will fail

3. Cache Exported Data

✅ Good:

// Check if already exported today
const cacheFile = `export-${today}.json`;
if (await fileExists(cacheFile)) {
return await readFromCache(cacheFile);
}

4. Schedule Off-Peak Exports

// Run exports during low-traffic hours
// 2 AM daily via cron
// 0 2 * * * node export-script.js

5. Validate Exported Data

async function validateExport(data: any[]) {
// Check for required fields
const requiredFields = ['id', 'linkId', 'clickedAt'];

for (const record of data) {
for (const field of requiredFields) {
if (!record[field]) {
throw new Error(`Missing required field: ${field}`);
}
}
}

console.log(`✅ Validated ${data.length} records`);
}

Troubleshooting

"Export limit exceeded"

Cause: Requested more records than plan allows

Fix:

  1. Reduce limit parameter
  2. Use pagination to fetch in chunks
  3. Upgrade plan for higher limits

"No data in export"

Causes:

  • No clicks in date range
  • Link filter excludes all data
  • Date range formatted incorrectly

Fix:

  1. Verify date format: YYYY-MM-DD
  2. Check that links have clicks in range
  3. Remove link filter to export all data

Export times out

Cause: Requesting too much data at once

Fix:

  1. Reduce date range (try 1 month at a time)
  2. Use pagination with smaller limit (1000-2000)
  3. Export specific links instead of all

CSV encoding issues

Cause: Special characters in data

Fix:

// Ensure UTF-8 encoding when saving
await fs.writeFile(filename, csv, { encoding: 'utf-8' });

Next Steps

  1. Export a sample dataset
  2. Set up automated daily/weekly exports
  3. Integrate with your BI tools
  4. Create custom analysis scripts
  5. Schedule regular data backups