Skip to main content

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.
documentId
string
required
The document ID to retrieve flashcards for
Returns: Array of flashcards with questions, answers, and attempt history
[]
Flashcard[]
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.
documentId
string
required
The document ID to generate flashcards from
Returns: Result of the batch creation operation
count
number
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

Build docs developers (and LLMs) love