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.
how agents use this
POST /api/scan
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);
}POST /api/migrate
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 commitmachine-readable spec
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
/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
})honest notes