# Moving a blog in from another system

Bring an existing blog across without breaking it: the same URLs, the original publish dates, drafts kept as drafts, and images moved onto your Writavo media library.

## What stays the same

- Every URL. You send each article's existing `slug`, and nothing is renamed for you.
- Every publish date. An imported article's first publish carries its original `published_at`, so feed order, structured data and sitemap dates are what they were.
- Drafts stay drafts. Only the articles you mark as published go live.
- Images. They are copied into your media library and the article bodies are rewritten to point at the copies, so the old host can be switched off later.
- Your source system. An import only reads what you export from it. Nothing is changed there.

## The three things that make an import faithful

1. **external_id** Your old system's id for each article. Look it up with `GET /articles?external_id=` before writing, and update what you find instead of creating a second copy. That is what makes an import safe to run twice, or to resume after it stops.
2. **slug** The last segment of the article's URL, sent exactly as it is today. It must match `^[a-z0-9]+(?:-[a-z0-9]+)*$`. There are no automatic redirects, so a slug that has to change is a decision to make before you import, not after.
3. **published_at on the first publish** `POST /articles/{id}/publish` accepts the original `published_at`, and optionally `content_updated_at`, in its body. Both must be in the past. They are honoured on an article's first publish only; after that the date is fixed, and resending the same value is a harmless no-op.

## The easy way: an AI assistant and the MCP server

The MCP server does the whole import for you from a single file, and handles the parts that are easy to get wrong: matching on `external_id`, re-hosting images, pacing requests under the rate limits, and resuming after an interruption.

1. **Connect** Add the server to your client, for example `claude mcp add writavo -- npx -y @writavo/mcp-server`. No key goes in the config.
2. **Sign in** Ask the assistant to log in. The `login` tool gives you a link and a short code. Open it, sign in or create your account, and approve access for your Site. If you are new, choose Bring my existing articles during onboarding: the AI pipeline then stays off until you switch it on.
3. **Export** Have the assistant read your old system, with your own access to it, and write a Writavo import file (below). Only the published articles and the drafts you want; not scrape records, revisions or anything the old system keeps for itself.
4. **Dry run** `import_content` with `dry_run: true` validates every article and reports each problem by `external_id`: slugs, lengths, references, dates, conflicts with what is already on the Site, and how many images will be copied. It writes nothing.
5. **Import** Run it again with `dry_run: false` and `confirm: true`. It works in batches and keeps its progress next to your file, so call it until it reports done.
6. **Check** Compare counts, slugs and dates with the old system before you point any traffic at Writavo.

## The import file

One JSON document. Authors, categories and tags are declared once and referenced from each article; they are matched to what already exists on your Site (categories and tags by slug, authors by name) and created when missing.

- `external_id` and `status` are required on every article. `status` is `published` or `draft`.
- A published article also needs its `title`, `slug`, `content` and original `published_at`. `content_updated_at` is optional and may not be earlier than `published_at`.
- `content` is markdown and is stored exactly as sent, apart from image URLs, which are rewritten to the copies in your media library.
- `faqs`, `key_takeaways`, `howto_steps` and `comparison` use the same shapes as the Article resource.
- `format` is optional and names one of your Site's content types by key (see `GET /content-types`).
- The JSON Schema is served at `https://writavo.com/schemas/import-v1.json`, and by the MCP server as the `writavo://import-format` resource.

## With the API directly

Everything the MCP tool does is ordinary API calls, so you can write your own importer. For each article:

1. **Find it** `GET /articles?external_id=<id>`. If it exists, `PATCH` it; if not, `POST /articles` with an `Idempotency-Key` and the `external_id`. A new article is always a draft.
2. **Move its images** Upload each image with the three step media flow and replace the old URLs in `content` and `featured_image_url` with the returned ones.
3. **Publish it with its original date** Only for articles that were public in the old system. Skip this call for an article that is already published.

## What does not move

> There are no redirects. If an article's URL has to change, add the redirect on your own site before you switch over, or keep the old slug.

- Revision history. Each article arrives as its current version.
- Comments, view counts and other engagement data.
- Author details beyond name, bio and avatar, and descriptions on categories and tags.
- A per-article canonical URL. Canonicals are derived from how your blog is delivered.

## Limits and cost

- Importing is part of the CMS, which is pay-as-you-go and the same on every plan: 10,000 documents and 25 GB of media are included. A plan is only needed for AI generation.
- Writes are limited to 120 a minute per key and media reservations to 60. The MCP tool paces itself; your own importer should honour `Retry-After`.
- A request body may be up to 1 MiB and an image up to 10 MB.
- Webhooks fire for imported articles like any others. Register your endpoints after the import if you do not want an event per article.

## After the import

Your content is in Writavo, and your old blog is still serving its own copy. Switching your live site over is a separate step: read the articles from the API on your own server, keeping your current URLs, and verify the two side by side before you change anything a reader or a search engine can see.
