Brand Studio docs
Engineering manualAPI route source

/api/brand-studio/retry-leg

Exact current route handler: hosted-app/app/api/brand-studio/retry-leg/route.js

Methods: POST. Input signals: JSON body. Literal response-status signals: none detected.

Source: hosted-app/app/api/brand-studio/retry-leg/route.js. Imports: ../../../../lib/all-rest/alpha-parking.mjs, ../../../../lib/comprehensive-pull-handler.mjs, ../../../../lib/retry-leg-handler.mjs, ../../../../lib/supabase-rest.mjs, next/server. Exact source is shown for inspection; its presence does not prove authentication, validation or live behavior.

All API routes · API and execution contract

// POST /api/brand-studio/retry-leg — re-run ONE source of a finished report.
//
// The thin shell. Every decision, every truth gate and every write lives in
// lib/retry-leg.mjs (pure) and lib/retry-leg-handler.mjs (Supabase), for the
// same reason app/api/comprehensive/route.js is thin: those two are testable
// with node:test and no Next request context, and this file is not.
//
// Body: { pull_id, leg }. Auth: the pull's own owner, via bearer token.
//
// AND IT NEVER CHARGES. Read lib/retry-leg.mjs's rule 2 before changing
// anything here: a transient inside a pull the customer already paid for is
// ours to absorb, so this whole path is deliberately unable to reach
// credits.mjs. The retry's real provider spend IS recorded on the row, stamped
// `retry: true` / `billed_to_customer: false`, because refusing to bill for
// something is not a reason to stop measuring it.
//
// maxDuration matches the comprehensive route's: a single retried leg is far
// short of it (the longest, ai_visibility, ran 51s on the pull that motivated
// this), but a reviews retry can wait out a 96s Trustpilot poll budget and
// must not be cut off mid-wait.
export const runtime = "nodejs";
export const dynamic = "force-dynamic";
export const maxDuration = 300;

import { NextResponse } from "next/server";
import { supabaseConfigFromEnv } from "../../../../lib/supabase-rest.mjs";
import { bearerTokenFromHeader } from "../../../../lib/comprehensive-pull-handler.mjs";
import { retryPullLeg } from "../../../../lib/retry-leg-handler.mjs";
import { alphaParkingRefusalForLeg, applyAlphaParkingToEnv, dataForSeoEvaluationEnabled } from "../../../../lib/all-rest/alpha-parking.mjs";

export async function POST(request) {
  const token = bearerTokenFromHeader(request.headers.get("authorization"));
  const body = await request.json().catch(() => ({}));
  const alphaRefusal = alphaParkingRefusalForLeg(body?.leg, { allowDataForSeo: dataForSeoEvaluationEnabled(process.env) });
  if (alphaRefusal) return NextResponse.json(alphaRefusal.body, { status: alphaRefusal.http_status });
  const result = await retryPullLeg({
    token,
    body,
    config: supabaseConfigFromEnv(process.env),
    env: applyAlphaParkingToEnv(process.env)
  });
  return NextResponse.json(result.body, { status: result.http_status });
}