Get Fireflies Transcript
Fetch full transcript with sentences, summary, and analytics
Source Code
import fs from "fs";
import path from "path";
const [outputPath, transcriptId] = process.argv.slice(2);
if (!outputPath || !transcriptId) {
console.error("Error: outputPath and transcriptId are required");
console.error("Usage: node script.js session/transcript.json <transcriptId>");
process.exit(1);
}
console.log(`Fetching transcript ${transcriptId}...`);
try {
// Build GraphQL query for full transcript
const query = `
query Transcript($id: String!) {
transcript(id: $id) {
id
title
date
duration
participants
organizer_email
transcript_url
speakers {
id
name
}
sentences {
index
speaker_name
speaker_id
text
raw_text
start_time
end_time
ai_filters {
task
question
sentiment
}
}
summary {
overview
action_items
keywords
gist
short_summary
bullet_gist
topics_discussed
outline
}
analytics {
sentiments {
positive_pct
neutral_pct
negative_pct
}
categories {
questions
tasks
metrics
date_times
}
speakers {
speaker_id
name
duration
duration_pct
word_count
words_per_minute
questions
filler_words
longest_monologue
monologues_count
}
}
meeting_attendees {
displayName
email
name
}
}
}
`;
const res = await fetch("https://api.fireflies.ai/graphql", {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: "Bearer PLACEHOLDER_TOKEN",
},
body: JSON.stringify({ query, variables: { id: transcriptId } }),
});
const data = await res.json();
if (data.errors) {
const errMsg = data.errors[0]?.message || "GraphQL error";
throw new Error(`Fireflies API: ${errMsg}`);
}
const transcript = data.data?.transcript;
if (!transcript) {
throw new Error(`Transcript not found: ${transcriptId}`);
}
console.log(` Title: ${transcript.title}`);
// Process transcript for readable output
const dateISO = transcript.date ? new Date(transcript.date).toISOString() : null;
const dateFormatted = transcript.date
? new Date(transcript.date).toLocaleDateString("en-US", {
weekday: "long",
year: "numeric",
month: "long",
day: "numeric",
})
: null;
// Format sentences with timestamps
const sentences = (transcript.sentences || []).map((s) => ({
index: s.index,
speaker: s.speaker_name || `Speaker ${s.speaker_id}`,
text: s.text,
startTime: s.start_time,
endTime: s.end_time,
startFormatted: formatTime(s.start_time),
isTask: s.ai_filters?.task || false,
isQuestion: s.ai_filters?.question || false,
sentiment: s.ai_filters?.sentiment || null,
}));
// Process speaker analytics
const speakerStats = (transcript.analytics?.speakers || []).map((s) => ({
name: s.name || `Speaker ${s.speaker_id}`,
durationSeconds: s.duration || 0,
durationPercent: s.duration_pct || 0,
wordCount: s.word_count || 0,
wordsPerMinute: s.words_per_minute || 0,
questions: s.questions || 0,
fillerWords: s.filler_words || 0,
longestMonologue: s.longest_monologue || 0,
monologueCount: s.monologues_count || 0,
}));
// Sort speakers by talk time (most to least)
speakerStats.sort((a, b) => b.durationPercent - a.durationPercent);
// Build output
const output = {
id: transcript.id,
title: transcript.title || "(Untitled meeting)",
date: dateISO,
dateFormatted,
durationMinutes: transcript.duration || null,
transcriptUrl: transcript.transcript_url || null,
participants: transcript.participants || [],
organizer: transcript.organizer_email || null,
attendees: (transcript.meeting_attendees || []).map((a) => ({
name: a.displayName || a.name || a.email?.split("@")[0],
email: a.email,
})),
speakers: (transcript.speakers || []).map((s) => s.name).filter(Boolean),
// Summary data
summary: {
overview: transcript.summary?.overview || null,
gist: transcript.summary?.gist || null,
shortSummary: transcript.summary?.short_summary || null,
bulletGist: transcript.summary?.bullet_gist || null,
actionItems: transcript.summary?.action_items || null,
keywords: transcript.summary?.keywords || null,
topicsDiscussed: transcript.summary?.topics_discussed || [],
outline: transcript.summary?.outline || null,
},
// Analytics (may be null on free tier)
analytics: transcript.analytics
? {
sentiment: {
positive: transcript.analytics.sentiments?.positive_pct || 0,
neutral: transcript.analytics.sentiments?.neutral_pct || 0,
negative: transcript.analytics.sentiments?.negative_pct || 0,
},
categories: {
questions: transcript.analytics.categories?.questions || 0,
tasks: transcript.analytics.categories?.tasks || 0,
metrics: transcript.analytics.categories?.metrics || 0,
dateTimes: transcript.analytics.categories?.date_times || 0,
},
speakers: speakerStats,
}
: null,
// Full transcript
sentenceCount: sentences.length,
sentences,
};
// Write output
const dir = path.dirname(outputPath);
if (dir && dir !== ".") fs.mkdirSync(dir, { recursive: true });
fs.writeFileSync(outputPath, JSON.stringify(output, null, 2));
console.log(`\nā Retrieved transcript`);
console.log(` Date: ${dateFormatted}`);
console.log(` Duration: ${transcript.duration || "?"} minutes`);
console.log(` Sentences: ${sentences.length}`);
console.log(` Speakers: ${output.speakers.length}`);
console.log(` Output: ${outputPath}`);
// Show summary preview
if (output.summary.gist) {
console.log(`\n Gist: ${output.summary.gist}`);
}
// Show speaker stats if available
if (speakerStats.length > 0) {
console.log(`\n Speaker breakdown:`);
speakerStats.slice(0, 4).forEach((s) => {
console.log(` ${s.name}: ${s.durationPercent.toFixed(0)}% (${s.wordCount} words)`);
});
}
console.log(
JSON.stringify({
success: true,
outputPath,
title: output.title,
sentenceCount: sentences.length,
hasAnalytics: !!output.analytics,
})
);
} catch (error) {
console.error("Failed:", error.message);
throw error;
}
// Helper to format seconds as MM:SS
function formatTime(seconds) {
if (!seconds && seconds !== 0) return null;
const mins = Math.floor(seconds / 60);
const secs = Math.floor(seconds % 60);
return `${mins}:${secs.toString().padStart(2, "0")}`;
}