diff --git a/src/Orbit.Api/Controllers/HabitsController.cs b/src/Orbit.Api/Controllers/HabitsController.cs
index c1ec285d..407d3f77 100644
--- a/src/Orbit.Api/Controllers/HabitsController.cs
+++ b/src/Orbit.Api/Controllers/HabitsController.cs
@@ -117,6 +117,35 @@ public async Task<IActionResult> GetDailySummary(
         return result.ToPayGateAwareResult(v => Ok(v));
     }
 
+    [HttpGet("retrospective")]
+    public async Task<IActionResult> GetRetrospective(
+        [FromQuery] string period,
+        [FromQuery] string language = "en",
+        CancellationToken cancellationToken = default)
+    {
+        var today = DateOnly.FromDateTime(DateTime.UtcNow);
+        var days = period.ToLowerInvariant() switch
+        {
+            "week" => 7,
+            "month" => 30,
+            "quarter" => 90,
+            "semester" => 180,
+            "year" => 365,
+            _ => 7
+        };
+        var dateFrom = today.AddDays(-days);
+
+        var query = new GetRetrospectiveQuery(
+            HttpContext.GetUserId(),
+            dateFrom,
+            today,
+            period,
+            language);
+
+        var result = await mediator.Send(query, cancellationToken);
+        return result.ToPayGateAwareResult(v => Ok(v));
+    }
+
     [HttpGet("{id:guid}")]
     public async Task<IActionResult> GetHabitById(Guid id, CancellationToken cancellationToken)
     {
diff --git a/src/Orbit.Api/Program.cs b/src/Orbit.Api/Program.cs
index 1b655610..9efbf03a 100644
--- a/src/Orbit.Api/Program.cs
+++ b/src/Orbit.Api/Program.cs
@@ -158,6 +158,9 @@
 // Daily summary always uses Gemini (free-text generation)
 builder.Services.AddHttpClient<ISummaryService, GeminiSummaryService>();
 
+// Retrospective always uses Gemini (free-text generation)
+builder.Services.AddHttpClient<IRetrospectiveService, GeminiRetrospectiveService>();
+
 // --- In-Memory Cache ---
 builder.Services.AddMemoryCache();
 
diff --git a/src/Orbit.Application/Common/PayGateService.cs b/src/Orbit.Application/Common/PayGateService.cs
index 1007b0fc..4ba259e5 100644
--- a/src/Orbit.Application/Common/PayGateService.cs
+++ b/src/Orbit.Application/Common/PayGateService.cs
@@ -70,6 +70,19 @@ public async Task<Result> CanUseDailySummary(Guid userId, CancellationToken ct =
         return Result.Success();
     }
 
+    public async Task<Result> CanUseRetrospective(Guid userId, CancellationToken ct = default)
+    {
+        var user = await userRepository.GetByIdAsync(userId, ct);
+        if (user is null)
+            return Result.Failure("User not found.");
+
+        var proOnly = await appConfig.GetAsync("RetrospectiveProOnly", true, ct);
+        if (proOnly && !user.HasProAccess)
+            return Result.PayGateFailure("Retrospectives are a Pro feature. Upgrade to unlock!");
+
+        return Result.Success();
+    }
+
     /// <summary>
     /// Returns the AI message limit for the given user (used by profile/subscription endpoints).
     /// </summary>
diff --git a/src/Orbit.Application/Habits/Queries/GetRetrospectiveQuery.cs b/src/Orbit.Application/Habits/Queries/GetRetrospectiveQuery.cs
new file mode 100644
index 00000000..d6b9d3d3
--- /dev/null
+++ b/src/Orbit.Application/Habits/Queries/GetRetrospectiveQuery.cs
@@ -0,0 +1,67 @@
+using MediatR;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.Extensions.Caching.Memory;
+using Orbit.Application.Common;
+using Orbit.Domain.Common;
+using Orbit.Domain.Entities;
+using Orbit.Domain.Interfaces;
+
+namespace Orbit.Application.Habits.Queries;
+
+public record RetrospectiveResponse(string Retrospective, bool FromCache);
+
+public record GetRetrospectiveQuery(
+    Guid UserId,
+    DateOnly DateFrom,
+    DateOnly DateTo,
+    string Period,
+    string Language) : IRequest<Result<RetrospectiveResponse>>;
+
+public class GetRetrospectiveQueryHandler(
+    IGenericRepository<Habit> habitRepository,
+    IPayGateService payGate,
+    IRetrospectiveService retrospectiveService,
+    IMemoryCache cache) : IRequestHandler<GetRetrospectiveQuery, Result<RetrospectiveResponse>>
+{
+    public async Task<Result<RetrospectiveResponse>> Handle(
+        GetRetrospectiveQuery request,
+        CancellationToken cancellationToken)
+    {
+        var gateCheck = await payGate.CanUseRetrospective(request.UserId, cancellationToken);
+        if (gateCheck.IsFailure)
+            return gateCheck.PropagateError<RetrospectiveResponse>();
+
+        var cacheKey = $"retro:{request.UserId}:{request.Period}:{request.DateFrom}:{request.Language}";
+
+        if (cache.TryGetValue(cacheKey, out string? cached) && cached is not null)
+            return Result.Success(new RetrospectiveResponse(cached, FromCache: true));
+
+        var habits = await habitRepository.FindAsync(
+            h => h.UserId == request.UserId && h.IsActive,
+            q => q.Include(h => h.Logs.Where(l => l.Date >= request.DateFrom && l.Date <= request.DateTo)),
+            cancellationToken);
+
+        var habitList = habits.ToList();
+
+        if (habitList.Count == 0)
+            return Result.Failure<RetrospectiveResponse>("No habits found for this period.");
+
+        var result = await retrospectiveService.GenerateRetrospectiveAsync(
+            habitList,
+            request.DateFrom,
+            request.DateTo,
+            request.Period,
+            request.Language,
+            cancellationToken);
+
+        if (result.IsFailure)
+            return Result.Failure<RetrospectiveResponse>(result.Error);
+
+        cache.Set(cacheKey, result.Value, new MemoryCacheEntryOptions
+        {
+            AbsoluteExpirationRelativeToNow = TimeSpan.FromHours(1)
+        });
+
+        return Result.Success(new RetrospectiveResponse(result.Value, FromCache: false));
+    }
+}
diff --git a/src/Orbit.Domain/Interfaces/IPayGateService.cs b/src/Orbit.Domain/Interfaces/IPayGateService.cs
index 617de3aa..39d39887 100644
--- a/src/Orbit.Domain/Interfaces/IPayGateService.cs
+++ b/src/Orbit.Domain/Interfaces/IPayGateService.cs
@@ -24,6 +24,11 @@ public interface IPayGateService
     /// </summary>
     Task<Result> CanUseDailySummary(Guid userId, CancellationToken ct = default);
 
+    /// <summary>
+    /// Checks if the user can use AI retrospectives (Pro-only feature).
+    /// </summary>
+    Task<Result> CanUseRetrospective(Guid userId, CancellationToken ct = default);
+
     /// <summary>
     /// Returns the AI message limit for the given user.
     /// </summary>
diff --git a/src/Orbit.Domain/Interfaces/IRetrospectiveService.cs b/src/Orbit.Domain/Interfaces/IRetrospectiveService.cs
new file mode 100644
index 00000000..302a7187
--- /dev/null
+++ b/src/Orbit.Domain/Interfaces/IRetrospectiveService.cs
@@ -0,0 +1,15 @@
+using Orbit.Domain.Common;
+using Orbit.Domain.Entities;
+
+namespace Orbit.Domain.Interfaces;
+
+public interface IRetrospectiveService
+{
+    Task<Result<string>> GenerateRetrospectiveAsync(
+        List<Habit> habits,
+        DateOnly dateFrom,
+        DateOnly dateTo,
+        string period,
+        string language,
+        CancellationToken cancellationToken = default);
+}
diff --git a/src/Orbit.Infrastructure/Services/GeminiRetrospectiveService.cs b/src/Orbit.Infrastructure/Services/GeminiRetrospectiveService.cs
new file mode 100644
index 00000000..e0e99516
--- /dev/null
+++ b/src/Orbit.Infrastructure/Services/GeminiRetrospectiveService.cs
@@ -0,0 +1,237 @@
+using System.Net.Http.Json;
+using System.Text.Json.Serialization;
+using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Options;
+using Orbit.Application.Habits.Services;
+using Orbit.Domain.Common;
+using Orbit.Domain.Entities;
+using Orbit.Domain.Interfaces;
+using Orbit.Infrastructure.Configuration;
+
+namespace Orbit.Infrastructure.Services;
+
+public sealed class GeminiRetrospectiveService(
+    HttpClient httpClient,
+    IOptions<GeminiSettings> options,
+    ILogger<GeminiRetrospectiveService> logger) : IRetrospectiveService
+{
+    private readonly GeminiSettings _settings = options.Value;
+
+    public async Task<Result<string>> GenerateRetrospectiveAsync(
+        List<Habit> habits,
+        DateOnly dateFrom,
+        DateOnly dateTo,
+        string period,
+        string language,
+        CancellationToken cancellationToken = default)
+    {
+        if (habits.Count == 0)
+            return Result.Failure<string>("No habits found for this period.");
+
+        var prompt = BuildRetrospectivePrompt(habits, dateFrom, dateTo, period, language);
+
+        logger.LogInformation(
+            "Calling Gemini API for retrospective (period: {Period}, from: {From}, to: {To}, language: {Language})...",
+            period, dateFrom, dateTo, language);
+
+        var request = new GeminiRequest
+        {
+            Contents =
+            [
+                new GeminiContent
+                {
+                    Parts = [new GeminiPart { Text = prompt }]
+                }
+            ],
+            GenerationConfig = new GeminiGenerationConfig
+            {
+                Temperature = 0.7
+            }
+        };
+
+        HttpResponseMessage? response = null;
+        int retryCount = 0;
+        const int maxRetries = 3;
+
+        while (retryCount <= maxRetries)
+        {
+            response = await httpClient.PostAsJsonAsync(
+                $"{_settings.BaseUrl}/models/{_settings.Model}:generateContent?key={_settings.ApiKey}",
+                request,
+                cancellationToken);
+
+            if (response.IsSuccessStatusCode || response.StatusCode != System.Net.HttpStatusCode.TooManyRequests)
+                break;
+
+            retryCount++;
+            if (retryCount <= maxRetries)
+            {
+                var delayMs = (int)Math.Pow(2, retryCount) * 1000;
+                logger.LogWarning("Rate limited by Gemini. Retrying in {DelayMs}ms (attempt {Retry}/{Max})...",
+                    delayMs, retryCount, maxRetries);
+                await Task.Delay(delayMs, cancellationToken);
+            }
+        }
+
+        if (!response!.IsSuccessStatusCode)
+        {
+            var errorBody = await response.Content.ReadAsStringAsync(cancellationToken);
+            logger.LogError("Gemini API returned {Status}: {Body}", response.StatusCode, errorBody);
+            return Result.Failure<string>($"Gemini API error: {response.StatusCode}");
+        }
+
+        var geminiResponse = await response.Content.ReadFromJsonAsync<GeminiResponse>(cancellationToken);
+        var text = geminiResponse?.Candidates?.FirstOrDefault()?.Content?.Parts?.FirstOrDefault()?.Text;
+
+        if (string.IsNullOrWhiteSpace(text))
+        {
+            logger.LogWarning("Gemini returned empty response for retrospective");
+            return Result.Failure<string>("Gemini returned empty response");
+        }
+
+        var trimmed = text.Trim();
+        if (trimmed.StartsWith("```"))
+        {
+            var lines = trimmed.Split('\n');
+            if (lines.Length >= 2)
+                trimmed = string.Join('\n', lines.Skip(1).Take(lines.Length - (lines[^1].TrimStart().StartsWith("```") ? 2 : 1)));
+        }
+
+        logger.LogInformation("Retrospective generated successfully ({Length} chars)", trimmed.Trim().Length);
+
+        return Result.Success(trimmed.Trim());
+    }
+
+    private static string BuildRetrospectivePrompt(
+        List<Habit> habits,
+        DateOnly dateFrom,
+        DateOnly dateTo,
+        string period,
+        string language)
+    {
+        var languageName = language.ToLowerInvariant() switch
+        {
+            "pt-br" or "pt" => "Brazilian Portuguese",
+            _ => "English"
+        };
+
+        var totalDays = dateTo.DayNumber - dateFrom.DayNumber + 1;
+
+        var habitLines = new List<string>();
+        var totalCompletions = 0;
+        var totalScheduled = 0;
+        var badHabitSlips = 0;
+
+        foreach (var habit in habits.Where(h => h.ParentHabitId is null))
+        {
+            var scheduledDates = HabitScheduleService.GetScheduledDates(habit, dateFrom, dateTo);
+            var scheduledCount = scheduledDates.Count;
+            var logs = habit.Logs.Where(l => l.Date >= dateFrom && l.Date <= dateTo).ToList();
+            var completedCount = logs.Count;
+
+            if (scheduledCount == 0 && completedCount == 0)
+                continue;
+
+            totalScheduled += scheduledCount;
+            totalCompletions += completedCount;
+
+            var rate = scheduledCount > 0 ? (int)Math.Round(100.0 * completedCount / scheduledCount) : 0;
+
+            if (habit.IsBadHabit)
+            {
+                badHabitSlips += completedCount;
+                habitLines.Add($"- {habit.Title} (bad habit): {completedCount} slips in {totalDays} days");
+            }
+            else
+            {
+                habitLines.Add($"- {habit.Title}: {completedCount}/{scheduledCount} completed ({rate}%)");
+            }
+
+            // Sub-habits
+            var children = habits.Where(h => h.ParentHabitId == habit.Id).ToList();
+            foreach (var child in children)
+            {
+                var childLogs = child.Logs.Where(l => l.Date >= dateFrom && l.Date <= dateTo).ToList();
+                var childScheduled = HabitScheduleService.GetScheduledDates(child, dateFrom, dateTo).Count;
+                var childRate = childScheduled > 0 ? (int)Math.Round(100.0 * childLogs.Count / childScheduled) : 0;
+                habitLines.Add($"  - {child.Title}: {childLogs.Count}/{childScheduled} ({childRate}%)");
+            }
+        }
+
+        var habitSection = habitLines.Count > 0
+            ? string.Join("\n", habitLines)
+            : "(no habit activity)";
+
+        var overallRate = totalScheduled > 0 ? (int)Math.Round(100.0 * totalCompletions / totalScheduled) : 0;
+
+        return $"""
+            You are a thoughtful habit coach writing a retrospective review.
+
+            Period: Last {totalDays} days ({period}) -- {dateFrom:MMMM d} to {dateTo:MMMM d, yyyy}
+            Total habits tracked: {habits.Count(h => h.ParentHabitId is null)}
+            Overall completion rate: {totalCompletions}/{totalScheduled} ({overallRate}%)
+            {(badHabitSlips > 0 ? $"Bad habit slips: {badHabitSlips}" : "")}
+
+            Per-habit breakdown:
+            {habitSection}
+
+            Write a retrospective with these sections (use these exact headings):
+            1. **Highlights** -- What went well, milestones reached, strong completion rates
+            2. **Missed Opportunities** -- What was skipped or neglected, patterns of avoidance
+            3. **Trends** -- Improving or declining patterns, consistency observations
+            4. **Suggestion** -- One specific, actionable tip for the next period
+
+            Rules:
+            - Be honest but encouraging -- celebrate real wins, acknowledge real gaps
+            - Reference specific habit names and numbers from the data
+            - Keep each section to 2-3 sentences max
+            - If a habit has 100% completion, call it out as a win
+            - If a habit has <50% completion, flag it kindly
+            - For bad habits, fewer slips = good progress
+            - Do NOT use emojis or JSON
+            - Use markdown bold for section headings only
+            - Write ONLY in {languageName}
+            """;
+    }
+
+    // --- Gemini API DTOs (same as GeminiSummaryService) ---
+
+    private record GeminiRequest
+    {
+        [JsonPropertyName("contents")]
+        public GeminiContent[] Contents { get; init; } = [];
+
+        [JsonPropertyName("generationConfig")]
+        public GeminiGenerationConfig? GenerationConfig { get; init; }
+    }
+
+    private record GeminiContent
+    {
+        [JsonPropertyName("parts")]
+        public GeminiPart[] Parts { get; init; } = [];
+    }
+
+    private record GeminiPart
+    {
+        [JsonPropertyName("text")]
+        public string Text { get; init; } = string.Empty;
+    }
+
+    private record GeminiGenerationConfig
+    {
+        [JsonPropertyName("temperature")]
+        public double Temperature { get; init; }
+    }
+
+    private record GeminiResponse
+    {
+        [JsonPropertyName("candidates")]
+        public GeminiCandidate[]? Candidates { get; init; }
+    }
+
+    private record GeminiCandidate
+    {
+        [JsonPropertyName("content")]
+        public GeminiContent? Content { get; init; }
+    }
+}
