migrate
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}/publishaccepts the originalpublished_at, and optionallycontent_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
logintool 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_contentwithdry_run: truevalidates every article and reports each problem byexternal_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: falseandconfirm: 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.
{
"format": "writavo-import",
"version": 1,
"source": {
"name": "Old blog",
"url": "https://example.com"
},
"authors": [
{
"ref": "jane",
"name": "Jane Doe",
"bio": "Writes about search.",
"avatar_url": "https://example.com/jane.png",
"is_ai_generated": false
}
],
"categories": [
{
"slug": "guides",
"name": "Guides"
}
],
"tags": [
{
"slug": "seo",
"name": "SEO"
}
],
"articles": [
{
"external_id": "oldcms:1042",
"status": "published",
"title": "How to plan a content calendar",
"slug": "how-to-plan-a-content-calendar",
"content": "## Start with the goal\n\n\n\n...",
"excerpt": "A calendar that survives contact with a real team.",
"seo_title": "How to plan a content calendar",
"seo_description": "A practical way to plan a quarter of posts.",
"featured_image": {
"url": "https://example.com/images/hero.png",
"alt": "A wall calendar"
},
"author": "jane",
"category": "guides",
"tags": [
"seo"
],
"published_at": "2024-03-18T09:00:00Z",
"content_updated_at": "2025-01-07T14:30:00Z"
},
{
"external_id": "oldcms:1107",
"status": "draft",
"title": "Working title: editorial style guide",
"content": "Notes so far..."
}
]
}external_idandstatusare required on every article.statusispublishedordraft.- A published article also needs its
title,slug,contentand originalpublished_at.content_updated_atis optional and may not be earlier thanpublished_at. contentis 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_stepsandcomparisonuse the same shapes as the Article resource.formatis optional and names one of your Site's content types by key (seeGET /content-types).- The JSON Schema is served at
https://writavo.com/schemas/import-v1.json, and by the MCP server as thewritavo://import-formatresource.
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,PATCHit; if not,POST /articleswith anIdempotency-Keyand theexternal_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
contentandfeatured_image_urlwith 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.
curl -X POST https://api.writavo.com/v1/articles/$ARTICLE_ID/publish \
-H "Authorization: Bearer $WRITAVO_API_KEY" \
-H "Content-Type: application/json" \
-d '{"published_at": "2024-03-18T09:00:00Z", "content_updated_at": "2025-01-07T14:30:00Z"}'What does not move#
- 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.