Overview
The Flashcard API enables AI-powered flashcard generation from PDF documents to help users study and memorize key concepts.
Procedures
getFlashcards
Retrieve all flashcards for a document, including attempt history. Available to document owners and collaborators.
The document ID to retrieve flashcards for
Returns: Array of flashcards with questions, answers, and attempt history
Unique flashcard identifier
Array of user attempts at answering this flashcard
Parts of the answer the user got right
Parts of the answer the user got wrong
Additional information or explanation
When the attempt was made
Example:
import { api } from "@/lib/api";
function FlashcardDeck({ documentId }: { documentId: string }) {
const { data: flashcards, isLoading } = api.flashcard.getFlashcards.useQuery({
documentId
});
if (isLoading) return <div>Loading flashcards...</div>;
if (!flashcards || flashcards.length === 0) {
return <div>No flashcards yet. Generate some!</div>;
}
return (
<div>
<h2>Study Flashcards ({flashcards.length})</h2>
{flashcards.map((card) => (
<div key={card.id} className="flashcard">
<div className="question">{card.question}</div>
<div className="answer">{card.answer}</div>
<div className="attempts">
Attempts: {card.flashcardAttempts.length}
</div>
</div>
))}
</div>
);
}
generateFlashcards
Generate flashcards from a document using AI. Available to document owners and collaborators.
The document ID to generate flashcards from
Returns: Result of the batch creation operation
Number of flashcards created
Flashcard generation:
- Uses AI to extract key concepts from the document
- Number of flashcards depends on document length and complexity
- Respects the user’s plan limits for pages per document
- May take several seconds to complete for large documents
Flashcard generation requires:
- Document must be successfully uploaded and accessible
- User’s plan allows for the document’s page count
- AI service must be available
Example:
import { api } from "@/lib/api";
function GenerateFlashcardsButton({ documentId }: { documentId: string }) {
const utils = api.useContext();
const generateMutation = api.flashcard.generateFlashcards.useMutation({
onSuccess: (result) => {
toast.success(`Generated ${result.count} flashcards!`);
// Refetch flashcards to show new ones
utils.flashcard.getFlashcards.invalidate({ documentId });
},
onError: (error) => {
toast.error("Failed to generate flashcards");
}
});
return (
<button
onClick={() => generateMutation.mutate({ documentId })}
disabled={generateMutation.isLoading}
>
{generateMutation.isLoading
? "Generating flashcards..."
: "Generate Flashcards"
}
</button>
);
}
Data Models
Flashcard Schema
interface Flashcard {
id: string;
question: string;
answer: string;
documentId: string;
createdAt: Date;
flashcardAttempts: FlashcardAttempt[];
}
FlashcardAttempt Schema
interface FlashcardAttempt {
id: string;
flashcardId: string;
userId: string;
createdAt: Date;
userResponse: string; // User's answer
correctResponse: string | null; // What they got right
incorrectResponse: string | null; // What they got wrong
moreInfo: string | null; // Additional explanation
}
Authorization
Flashcard procedures are available to:
- Document owners
- All collaborators (regardless of role)
This allows entire teams to study together and see each other’s progress.
// Authorization check
const doc = await ctx.prisma.document.findUnique({
where: {
id: input.documentId,
OR: [
{ ownerId: ctx.session.user.id },
{
collaborators: {
some: {
userId: ctx.session.user.id,
},
},
},
],
},
});
Plan Limits
Flashcard generation respects user plan limits:
import { PLANS } from "@/lib/constants";
const maxPagesAllowed = PLANS[user.plan].maxPagesPerDoc;
// Plans:
// - FREE: Limited pages per document
// - FREE_PLUS: More pages allowed
// - PRO: Maximum pages allowed
Use Cases
Study Mode
function StudyMode({ documentId }: { documentId: string }) {
const { data: flashcards } = api.flashcard.getFlashcards.useQuery({ documentId });
const [currentIndex, setCurrentIndex] = useState(0);
const [showAnswer, setShowAnswer] = useState(false);
const currentCard = flashcards?.[currentIndex];
return (
<div className="study-card">
<h3>Question {currentIndex + 1} of {flashcards?.length}</h3>
<p>{currentCard?.question}</p>
{showAnswer && <p><strong>Answer:</strong> {currentCard?.answer}</p>}
<button onClick={() => setShowAnswer(!showAnswer)}>
{showAnswer ? "Hide" : "Show"} Answer
</button>
<button onClick={() => setCurrentIndex(currentIndex + 1)}>
Next Card
</button>
</div>
);
}
Progress Tracking
function FlashcardProgress({ documentId }: { documentId: string }) {
const { data: flashcards } = api.flashcard.getFlashcards.useQuery({ documentId });
const stats = {
total: flashcards?.length ?? 0,
attempted: flashcards?.filter(c => c.flashcardAttempts.length > 0).length ?? 0,
mastered: flashcards?.filter(c =>
c.flashcardAttempts.length >= 3 &&
c.flashcardAttempts.every(a => a.incorrectResponse === null)
).length ?? 0,
};
return (
<div>
<h3>Study Progress</h3>
<p>Total Cards: {stats.total}</p>
<p>Attempted: {stats.attempted}</p>
<p>Mastered: {stats.mastered}</p>
<ProgressBar value={stats.mastered} max={stats.total} />
</div>
);
}
Spaced Repetition
function SpacedRepetition({ documentId }: { documentId: string }) {
const { data: flashcards } = api.flashcard.getFlashcards.useQuery({ documentId });
const cardsToReview = flashcards?.filter(card => {
const lastAttempt = card.flashcardAttempts[card.flashcardAttempts.length - 1];
if (!lastAttempt) return true; // Never attempted
const daysSinceAttempt =
(Date.now() - new Date(lastAttempt.createdAt).getTime()) / (1000 * 60 * 60 * 24);
// Review if it's been more than 3 days or if incorrect
return daysSinceAttempt > 3 || lastAttempt.incorrectResponse !== null;
});
return (
<div>
<h3>Cards Due for Review: {cardsToReview?.length}</h3>
{cardsToReview?.map(card => (
<FlashcardReview key={card.id} card={card} />
))}
</div>
);
}
Error Handling
Common errors:
UNAUTHORIZED - User doesn’t have access to the document
INTERNAL_SERVER_ERROR - AI service error or document processing failure
const generateMutation = api.flashcard.generateFlashcards.useMutation({
onError: (error) => {
if (error.data?.code === "UNAUTHORIZED") {
toast.error("You don't have access to this document");
} else if (error.data?.code === "INTERNAL_SERVER_ERROR") {
toast.error("Failed to generate flashcards. Please try again.");
}
},
});
Best Practices
Generate Once
Generate flashcards once per document to avoid duplicates and API costs
Cache Results
Flashcard data is cached by React Query. Use refetch when needed
Track Progress
Use attempt history to implement spaced repetition and progress tracking
Handle Long Operations
Show loading states during generation - it may take 10-30 seconds