Fetch the actual latest version id from the API (bulletproof)
In the same environment where your server runs:

export REPLICATE_API_TOKEN=r8_your_token
curl -s -H "Authorization: Token $REPLICATE_API_TOKEN" \
  https://api.replicate.com/v1/models/recraft-ai/recraft-v3 | jq '.latest_version.id,.urls.doc'


Copy the printed latest_version.id (a long hash).

Paste it into .env.local:

REPLICATE_RECRAFT_V3_VERSION=<that-long-hash>


Restart your dev server.

Sanity curl with that version

curl -s -X POST https://api.replicate.com/v1/predictions \
  -H "Authorization: Token $REPLICATE_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d "{\"version\": \"${REPLICATE_RECRAFT_V3_VERSION}\", \"input\": {\"prompt\": \"minimal monoline coffee bean logo, vector, no text\", \"output_format\": \"svg\"}}"


If you get a JSON with an id, you’re good.

Full drop-in route (safer): auto-resolve the version id

Replace your API route with this. It will use the env version if present, otherwise ask Replicate for the latest (and gives you clear error logs if permission is the issue).

src/app/api/vector/recraft/route.ts

export const runtime = "nodejs"; // ensure Node runtime

import { NextRequest, NextResponse } from "next/server";

const REPLICATE_TOKEN = process.env.REPLICATE_API_TOKEN!;
let VERSION = process.env.REPLICATE_RECRAFT_V3_VERSION || ""; // optional; we can auto-resolve

async function getLatestVersion(): Promise<string> {
  const resp = await fetch("https://api.replicate.com/v1/models/recraft-ai/recraft-v3", {
    headers: { Authorization: `Token ${REPLICATE_TOKEN}` },
    // avoid caches in some hosts
    cache: "no-store",
  });

  if (!resp.ok) {
    const t = await resp.text();
    throw new Error(`Failed to read model info (${resp.status}): ${t}`);
  }

  const json = await resp.json();
  const id = json?.latest_version?.id;
  if (!id) throw new Error("No latest_version.id on model payload");
  return id as string;
}

export async function POST(req: NextRequest) {
  try {
    if (!REPLICATE_TOKEN) {
      return NextResponse.json({ error: "Missing REPLICATE_API_TOKEN" }, { status: 500 });
    }

    const { prompt } = await req.json();
    if (!prompt || typeof prompt !== "string") {
      return NextResponse.json({ error: "Missing prompt" }, { status: 400 });
    }

    // Resolve version if not provided or clearly invalid length
    if (!VERSION || VERSION.length < 16) {
      try {
        VERSION = await getLatestVersion();
        console.log("[recraft] Resolved latest version:", VERSION);
      } catch (e: any) {
        return NextResponse.json({ error: `Could not resolve version: ${e.message}` }, { status: 502 });
      }
    }

    // Kick off prediction
    const createRes = await fetch("https://api.replicate.com/v1/predictions", {
      method: "POST",
      headers: {
        Authorization: `Token ${REPLICATE_TOKEN}`,
        "Content-Type": "application/json",
      },
      body: JSON.stringify({
        version: VERSION,
        input: {
          prompt,
          output_format: "svg",
        },
      }),
    });

    // Better error visibility for 401/422
    if (!createRes.ok) {
      const text = await createRes.text();
      console.error("[recraft] create failed", createRes.status, text);
      return NextResponse.json({ error: `Create failed: ${text}` }, { status: createRes.status || 502 });
    }

    const start = await createRes.json();
    const id: string = start.id;
    if (!id) return NextResponse.json({ error: "No prediction id" }, { status: 502 });

    // Poll until done
    const timeoutAt = Date.now() + 90_000;
    let svgUrl: string | null = null;

    while (Date.now() < timeoutAt) {
      await sleep(1800);
      const stRes = await fetch(`https://api.replicate.com/v1/predictions/${id}`, {
        headers: { Authorization: `Token ${REPLICATE_TOKEN}` },
        cache: "no-store",
      });
      if (!stRes.ok) {
        const t = await stRes.text();
        console.error("[recraft] status failed", stRes.status, t);
        return NextResponse.json({ error: `Status failed: ${t}` }, { status: stRes.status || 502 });
      }
      const data = await stRes.json();

      if (data.status === "succeeded") {
        const out = data.output;
        if (Array.isArray(out)) {
          const candidate = out.find((u: unknown) => typeof u === "string" && u.toLowerCase().endsWith(".svg"));
          if (candidate) svgUrl = candidate as string;
        } else if (typeof out === "string" && out.toLowerCase().endsWith(".svg")) {
          svgUrl = out;
        }
        break;
      }
      if (data.status === "failed") {
        return NextResponse.json({ error: "Generation failed" }, { status: 502 });
      }
    }

    if (!svgUrl) return NextResponse.json({ error: "Timed out or missing SVG URL" }, { status: 504 });

    return NextResponse.json({ svgUrl, predictionId: id, version: VERSION });
  } catch (err: any) {
    console.error("[recraft] route error:", err);
    return NextResponse.json({ error: err?.message ?? "Server error" }, { status: 500 });
  }
}

function sleep(ms: number) {
  return new Promise((r) => setTimeout(r, ms));
}

Env (server)
REPLICATE_API_TOKEN=r8_************************
# Optional (route will auto-resolve if omitted):
REPLICATE_RECRAFT_V3_VERSION=aaaaaaaa-bbbb-cccc-dddd-eeeeffffffff

If you still see 422 after this

Your account likely hasn’t accepted access for the model. Go back to the model page and click Use with API once while logged in to the same Replicate account that created your token.

Double-check you didn’t paste someone else’s “version” they found online; use the API call above to fetch the exact latest id tied to your access.

Make sure you restarted the app after env changes.

Do those steps and fire Generate Logo again. If it barks, copy me the response body we log from create failed and I’ll smack the last gremlin.

You said:
V3
ChatGPT said:

Got it — Recraft V3. Here’s the fastest way to lock the correct version so the 422 dies:

Do this now (2 minutes)

Enable the model for your account

Open: https://replicate.com/recraft-ai/recraft-v3

Click Use with API (grants your token permission).

Fetch the exact version id from Replicate (bulletproof)

export REPLICATE_API_TOKEN=r8_your_token_here
curl -s -H "Authorization: Token $REPLICATE_API_TOKEN" \
  https://api.replicate.com/v1/models/recraft-ai/recraft-v3 \
  | jq -r '.latest_version.id'


Copy the long hash it prints.

Put it in your env

REPLICATE_API_TOKEN=r8_****************
REPLICATE_RECRAFT_V3_VERSION=<the-long-hash-from-step-2>


Restart your dev server.

Sanity test

curl -s -X POST https://api.replicate.com/v1/predictions \
  -H "Authorization: Token $REPLICATE_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d "{\"version\": \"${REPLICATE_RECRAFT_V3_VERSION}\", \"input\": {\"prompt\": \"minimal monoline coffee bean logo, vector, no text\", \"output_format\": \"svg\"}}"


If you get JSON with an "id", you’re good. Hit Generate Logo in-app.

If you’re using my auto-resolve route

If you pasted the version-auto-resolver I gave you, you can omit the env var and it will fetch the latest for you. Just make sure the token is set and you restarted.