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
- Go to Analytics → Overview
- Click "Export Data" button
- 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
- Click "Export"
- 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:
| Parameter | Type | Required | Description | Default |
|---|---|---|---|---|
startDate | string (YYYY-MM-DD) | No | Export start date | 30 days ago |
endDate | string (YYYY-MM-DD) | No | Export end date | Today |
linkId | string (UUID) | No | Filter by specific link | All links |
format | json | csv | No | Export format | json |
limit | number | No | Max records (1-10000) | 1000 |
offset | number | No | Pagination offset | 0 |
Requires: Member, Admin, or Owner role
Export Fields
All exports include the following fields:
Click Event Data
| Field | Type | Description |
|---|---|---|
id | string | Unique click event ID |
linkId | string | Associated link ID |
shortCode | string | Link's short code |
linkTitle | string | Link title |
originalUrl | string | Destination URL |
clickedAt | timestamp | When click occurred (UTC) |
User Information
| Field | Type | Description |
|---|---|---|
ipAddress | string | User's IP address |
userAgent | string | Full user-agent string |
Device & Platform
| Field | Type | Description |
|---|---|---|
deviceType | string | mobile, desktop, or tablet |
platform | string | iOS, Android, Windows, macOS, Linux |
browser | string | Chrome, Safari, Firefox, Edge, etc. |
browserVersion | string | Browser version number |
os | string | Operating system |
osVersion | string | OS version number |
Geographic Data
| Field | Type | Description |
|---|---|---|
countryCode | string | ISO 3166-1 alpha-2 country code |
countryName | string | Full country name |
region | string | State/province |
city | string | City name |
timezone | string | IANA timezone identifier |
latitude | number | Geographic latitude |
longitude | number | Geographic longitude |
Campaign Tracking
| Field | Type | Description |
|---|---|---|
utmSource | string | Campaign source |
utmMedium | string | Campaign medium |
utmCampaign | string | Campaign name |
utmTerm | string | Campaign term (optional) |
utmContent | string | Campaign content (optional) |
referrer | string | HTTP 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);
2. Link-Specific Export
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
| Plan | Max Records per Export | Max Exports per Day |
|---|---|---|
| Free | 1,000 | 10 |
| Pro | 10,000 | 100 |
| Enterprise | Unlimited | Unlimited |
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:
- Reduce
limitparameter - Use pagination to fetch in chunks
- 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:
- Verify date format:
YYYY-MM-DD - Check that links have clicks in range
- Remove link filter to export all data
Export times out
Cause: Requesting too much data at once
Fix:
- Reduce date range (try 1 month at a time)
- Use pagination with smaller
limit(1000-2000) - 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' });
Related Guides
- Analytics Dashboard - Understanding your analytics
- Link Analytics API - API reference
- UTM Parameters - Campaign tracking setup
Next Steps
- Export a sample dataset
- Set up automated daily/weekly exports
- Integrate with your BI tools
- Create custom analysis scripts
- Schedule regular data backups