for agents·JSON API · no auth · stateless

migrare for AI agents

The migration API is plain JSON over HTTP. No SDK, no auth, no session. An agent can call /api/scan with a base64 zip and get back a structured lock-in report in one round trip.

✓ safePre-edit context
whenBefore touching any files in a Lovable repo.
howCall /api/scan with the project zip. Feed the signal list into your context. You now know exactly which files have lock-in, what kind, and where — before writing a single line.
✓ safeStructured diff input
whenYou want pre-computed transforms rather than deriving them yourself.
howCall /api/migrate with dryRun: false. Get back {files: [{path, content}]}. Treat each entry as a suggested diff. Review, then write via your own file tools.
✗ avoidAutonomous apply
whenCalling /api/migrate and writing output directly to disk without a human checkpoint.
howDon't. The scan report is designed to inform a decision, not automate one. Migrate outputs a diff, not a commit.

Send a base64-encoded zip. Get back a structured scan report. Read-only — nothing is stored, no side effects.

request body

{
  source: {
    zip:  string,   // base64-encoded .zip bytes
    name: string    // filename, e.g. "my-app.zip"
  }
}

response shape

{
  platform:         "lovable" | "unknown",
  confidence:       "high" | "medium" | "low",
  fileCount:        number,
  detectionSignals: string[],
  signals: [
    {
      id:          string,
      platform:    string,
      category:    "build-config" | "state-entanglement"
                 | "auth-coupling" | "environment-bleed"
                 | "proprietary-api",
      severity:   "error" | "warning" | "info",
      confidence: "high" | "medium" | "low",
      location:   { file: string, line?: number },
      description: string,
      suggestion:  string
    }
  ],
  summary: {
    bySeverity:          { error: number, warning: number, info: number },
    byCategory:          Record<string, number>,
    migrationComplexity: "straightforward" | "moderate" | "requires-manual",
    totalSignals:        number
  }
}

pseudo-code pattern

// read zip into base64
const zip = fs.readFileSync("my-app.zip");
const b64 = zip.toString("base64");

// call scan
const res = await fetch("https://migrare.creadev.org/api/scan", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({ source: { zip: b64, name: "my-app.zip" } }),
});

const report = await res.json();

// use signal list as context before editing
for (const signal of report.signals) {
  console.log(signal.severity, signal.location.file, signal.description);
}

Applies transforms and returns the modified files as {path, content} pairs. Treat the output as a diff to review, not a blind write. Use dryRun: true to see what would change without committing to it.

request body

{
  source: {
    zip:  string,   // base64-encoded .zip bytes
    name: string
  },
  dryRun?:       boolean,  // default false — true returns log without file content
  targetAdapter?: "vite" | "nextjs"
}

response shape

{
  platform:     string,
  confidence:   string,
  dryRun:       boolean,
  duration:     number,          // ms
  signals:      Signal[],        // same as /api/scan
  summary:      Summary,
  transformLog: [
    {
      transform: string,         // e.g. "remove-lovable-tagger"
      file:      string,
      action:    "modified" | "created" | "deleted",
      meta?:     Record<string, unknown>
    }
  ],
  files: [
    { path: string, content: string }  // only modified files
  ],
  errors: string[]
}

recommended agent pattern

// 1. scan first — understand what you're dealing with
const scan = await callScan(zip);
if (scan.summary.migrationComplexity === "requires-manual") {
  // surface to human before proceeding
}

// 2. dry run — confirm transforms match expectations
const dry = await callMigrate(zip, { dryRun: true });
// show transformLog to human or include in context

// 3. apply — get file diffs
const result = await callMigrate(zip, { dryRun: false });

// 4. write via your own tools — don't blind-apply
for (const file of result.files) {
  // review diff, then write
  await myFileTools.write(file.path, file.content);
}

// 5. human checkpoint before commit

GET /api/spec returns a JSON document describing all endpoints, their request shapes, and response shapes. Stable across patch versions.

fetch("https://migrare.creadev.org/api/spec")
  .then(r => r.json())
  .then(spec => { /* endpoints, shapes, version */ })

/llms.txt is a plain-text summary of what migrare is and how to use its API — following the emerging llms.txt convention. Fetch it at the start of a session to give an agent instant context without reading the full docs.

fetch("https://migrare.creadev.org/llms.txt")
  .then(r => r.text())
  .then(ctx => {
    // prepend to system prompt or tool description
  })
Zip sizeThe edge function handles typical Lovable exports (< 5 MB unzipped) comfortably. Very large monorepos may hit Workers CPU limits. Run the CLI locally for those.
StatelessNothing is persisted between calls. Every request is independent. There is no session, no job ID, no polling — scan and migrate are synchronous and return immediately.
No authThe API is open. Rate limiting is handled by Cloudflare at the edge. If you're calling this in a tight loop from automation, add a pause between requests.
Transforms are surgicalThe engine only touches files it has explicit transforms for. It does not rewrite your whole app. Unknown patterns are flagged as signals, not auto-fixed.
Migrate ≠ commitThe files array is a suggested diff. migrare has no access to your repo, no git integration, and no ability to commit anything. That step is always yours.