Appearance
Backup and import endpoints
These REST routes export and restore backups and import data from other link plugins. Each one is a job: you start it, call a progress route until it finishes, and can cancel it.
Sign-in, the response envelope and errors are in the REST API overview.
Every route here needs an administrator (manage_options, Permissions). All paths are relative to /wp-json/link-manager-pro/v1. The plugin's own screens call these routes, so build on them with care.
A backup file is JSON. Backup file format describes what it holds. The same jobs run from the command line: WP-CLI for developers.
Export a backup
An export is a job that runs in batches. Call POST /settings/backup/export/start with the items you want. Then call POST /settings/backup/export/progress with the returned job_id. Each call writes one batch of up to 500 rows. Repeat until status is done. The last answer holds a download_url, and you fetch the file from it.
The job payload that the start and progress routes return:
| Field | Type | What it holds |
|---|---|---|
job_id | string | The job ID (a UUID). Send it to the progress route. |
type | string | export for a backup, import for a restore. |
status | string | running, done, or failed. A restore can also be cancelled. |
total | integer | Rows to write, summed over all items. |
processed | integer | Rows written so far. |
percent | integer | processed as a percentage of total, 0 to 100. A job with no rows reads 100 once done. |
current_item | string or null | The item being written now. null when none is left. |
items | array of strings | The items in the job, in backup order. |
items_total | object | Rows per item, keyed by item. |
items_processed | object | Rows written per item, keyed by item. |
error | string or null | The failure message, or null. |
download_url | string | Only when status is done. See Download the finished file. |
file_name | string | Only when status is done. The file name, link-manager-pro-backup-YYYYMMDD-HHMMSS.json (UTC). |
The item keys are categories, tags, links, link_rules, clicks, affiliates, campaigns, link_health, redirects, settings, webhooks and posts. The plugin lists a job's items in this order whatever order you send. Choosing links adds the item link_meta to the job.
Start an export
POST /settings/backup/export/start
Creates an export job for the items you choose. It writes nothing yet.
Permission: manage_options (Permissions)
Body parameters: (JSON)
| Name | Type | Required | Default | What it does |
|---|---|---|---|---|
items | array of strings | Yes | none | The items to export. Keys that are not in the list above are dropped. A value that is not an array counts as empty. |
Response: 200. data is the job payload with type set to export and status set to running.
bash
curl -X POST \
-u USER:APPLICATION_PASSWORD \
-H "Content-Type: application/json" \
-d '{"items":["links","categories","tags"]}' \
"https://example.com/wp-json/link-manager-pro/v1/settings/backup/export/start"json
{
"success": true,
"data": {
"job_id": "5f1c2a9e-3b7d-4e2a-9c1f-0a8d6b4e7f21",
"type": "export",
"status": "running",
"total": 1234,
"processed": 0,
"percent": 0,
"current_item": "categories",
"items": ["categories", "tags", "links", "link_meta"],
"items_total": { "categories": 4, "tags": 7, "links": 1200, "link_meta": 23 },
"items_processed": { "categories": 0, "tags": 0, "links": 0, "link_meta": 0 },
"error": null
}
}Errors:
| Status | When |
|---|---|
400 | None of the items you sent is a valid item. The code is backup_items_required. |
409 | A restore is unfinished. Finish or cancel it first. The code is backup_import_running. |
Run an export batch
POST /settings/backup/export/progress
Writes the next batch of the job and returns the new progress. Call it until status is done.
Permission: manage_options (Permissions)
Body parameters: (JSON)
| Name | Type | Required | Default | What it does |
|---|---|---|---|---|
job_id | string | Yes | none | The job_id from the start route. |
Response: 200. data is the job payload. A job that is already done returns its payload again with a fresh download_url.
json
{
"success": true,
"data": {
"job_id": "5f1c2a9e-3b7d-4e2a-9c1f-0a8d6b4e7f21",
"type": "export",
"status": "done",
"total": 1234,
"processed": 1234,
"percent": 100,
"current_item": null,
"items": ["categories", "tags", "links", "link_meta"],
"items_total": { "categories": 4, "tags": 7, "links": 1200, "link_meta": 23 },
"items_processed": { "categories": 4, "tags": 7, "links": 1200, "link_meta": 23 },
"error": null,
"download_url": "https://example.com/wp-admin/admin-ajax.php?action=link_manager_pro_download_backup&job_id=5f1c2a9e-3b7d-4e2a-9c1f-0a8d6b4e7f21&_wpnonce=1a2b3c4d5e",
"file_name": "link-manager-pro-backup-20261023-101500.json"
}
}Errors:
| Status | When |
|---|---|
400 | job_id is empty. The code is backup_job_required. |
404 | No job has that ID. The code is backup_job_not_found. |
500 | The job failed. The message says why, for example backup_export_fetch_failed (rows could not be read) or backup_job_failed. |
507 | The server ran out of disk space while writing. The code is backup_disk_full. |
Download the finished file
GET /wp-admin/admin-ajax.php?action=link_manager_pro_download_backup&job_id={job_id}&_wpnonce={nonce}
Sends the finished backup file. This is an admin-ajax request, not a REST route, so it needs a logged-in browser session (the login cookie). Use the download_url from the done payload as it is.
Permission: manage_options.
Query parameters:
| Name | Type | Required | Default | What it does |
|---|---|---|---|---|
action | string | Yes | none | Always link_manager_pro_download_backup. |
job_id | string | Yes | none | The export job ID. |
_wpnonce | string | Yes | none | A nonce for the action lmp_backup_download_{job_id}. The download_url already carries it. |
Response: 200 with the file as the body. The headers are Content-Type: application/json, Content-Disposition: attachment; filename="link-manager-pro-backup-....json", Content-Length and Cache-Control: no-cache, no-store, must-revalidate. The body is the backup file (Backup file format).
Errors:
| Status | When |
|---|---|
403 | The user is not an administrator, or the nonce is missing, wrong or expired. |
404 | The job does not exist, is not a finished export, or its file is gone. |
The server keeps the file for one day after the job's last activity. After that the daily clean-up deletes it, and you export again.
Export the links as CSV
GET /settings/backup/export/csv
Streams every short link as a CSV file. This is a spreadsheet list, not a restorable backup.
Permission: manage_options (Permissions)
Query parameters: None.
Response: 200 with a CSV body, not the JSON envelope. The headers are Content-Type: text/csv; charset=utf-8, Content-Disposition: attachment; filename="link-manager-pro-links-YYYY-MM-DD.csv" (the UTC date) and Cache-Control: no-cache, no-store, must-revalidate. Rows run from the highest link ID down. A cell that starts with =, +, -, @, a tab or a carriage return gets a leading ' so spreadsheets do not run it as a formula.
| Column | What it holds |
|---|---|
ID | The short link ID. |
Slug | The slug. |
Target URL | The destination. |
Redirect Type | The HTTP status, 301 when the link has none. |
Source | Where the link came from, such as manual. |
Click Count | The stored click total, 0 when none. |
Autolink | yes or no. |
Keywords | The auto-link keywords joined with a comma and a space. |
Created At | The creation date and time, written the way the link model prints it. Empty when none. |
bash
curl -u USER:APPLICATION_PASSWORD -o links.csv \
"https://example.com/wp-json/link-manager-pro/v1/settings/backup/export/csv"text
ID,Slug,"Target URL","Redirect Type",Source,"Click Count",Autolink,Keywords,"Created At"
42,blue-widget,https://example.com/products/blue-widget,301,manual,128,yes,"blue widget, widget",2026-10-01T09:30:00.000Restore a backup
A restore is also a job. You send the file and the items to restore, then call the progress route until it ends. The plugin keeps a safety copy of each item before it clears it. If a batch fails, or you cancel, it puts the copies back. Only one restore can run at a time, and a restore blocks new exports.
A small file goes to POST /settings/backup/import/start as the field file. A larger file goes up in parts to POST /settings/backup/import/chunk first. The last part returns an upload_id. You send that to the start route instead of the file.
The flow:
- Send the file, or upload its parts and keep the
upload_id. POST /settings/backup/import/startwithitemsandfileorupload_id. The plugin checks the file here. A refused file never touches your data.POST /settings/backup/import/progressuntilstatusisdone. Each call restores one batch of up to 500 rows (10 forposts).- To stop, call
POST /settings/backup/import/cancel. A failed batch rolls back on its own and returns500.
If a page closes mid-restore, GET /settings/backup/import/active returns the running job so you can resume it.
The restore job uses the same fields as the export job payload, with type set to import. A done restore adds these:
| Field | Type | What it holds |
|---|---|---|
message | string | Import completed successfully. |
notes | string | Only when something was skipped. A plain sentence, for example about posts that were not restored or a short link prefix that was left out. |
A progress call can also return busy set to true. That means another request is restoring a batch right now. Wait and call again.
Restore rules you can hit as errors:
- The file must be a
.jsonfile with JSON content and aschema_versionthe plugin knows. postscan only be restored on the site the backup came from.- A backup from another site must include the items that depend on each other. Restoring
linkswithoutlink_rules,clicks,redirects,categoriesandtagsis refused when this site has rows in them.
Upload one part of a backup
POST /settings/backup/import/chunk
Adds one part of a large backup file. Send the parts in order, as multipart/form-data.
Permission: manage_options (Permissions)
Body parameters: (multipart form, not JSON)
| Name | Type | Required | Default | What it does |
|---|---|---|---|---|
upload_id | string | No | empty | Empty on the first part. On later parts, the upload_id the previous answer returned. |
index | integer | Yes | 0 | The part number, counting from 0. It must equal the next_index of the last answer. |
total | integer | Yes | 0 | How many parts the upload has. Must be 1 or more, and above index. |
size | integer | Yes | 0 | The whole file's size in bytes. Must be above 0 and at most 1073741824 (1 GB). Read on the first part. |
name | string | Yes | empty | The file name. It must end in .json. Read on the first part. |
part | file | Yes | none | The bytes of this part. It can be no larger than the server's upload limit (wp_max_upload_size()). |
The plugin's own screen sends parts of up to 1 MB, or the server limit minus 64 KB when that is smaller. It halves a part the server refuses, down to 128 KB.
Response: 200. data holds:
| Field | Type | What it holds |
|---|---|---|
upload_id | string | The upload ID (a UUID). Send it with every later part and with the start route. |
next_index | integer | The index the next part must carry. |
bytes | integer | Bytes stored so far. |
size | integer | The whole file's size in bytes. |
complete | boolean | true when bytes equals size. |
bash
curl -X POST \
-u USER:APPLICATION_PASSWORD \
-F "upload_id=" -F "index=0" -F "total=3" -F "size=2621440" -F "name=backup.json" \
-F "[email protected]" \
"https://example.com/wp-json/link-manager-pro/v1/settings/backup/import/chunk"json
{
"success": true,
"data": {
"upload_id": "9b2e41c0-7a3d-4f6e-8d15-3c0b7a9e2f64",
"next_index": 1,
"bytes": 1048576,
"size": 2621440,
"complete": false
}
}Errors:
| Status | When |
|---|---|
400 | The part is missing or empty (backup_upload_part_missing), the transfer failed (backup_file_upload_failed), the name does not end in .json (backup_file_extension), or the sizes do not add up (backup_upload_invalid). |
403 | Another user started this upload. The code is backup_upload_forbidden. |
404 | The upload_id is unknown or expired, or index is not 0 on a first part. The code is backup_upload_not_found. |
409 | The part arrived out of order. The code is backup_upload_part_order. The error data holds expected_index and bytes so you can resume from there. |
413 | The part is larger than the server takes (backup_upload_part_too_large, with limit in the error data), or the file is over 1 GB (backup_upload_too_large). |
500 | The part could not be stored. The code is backup_file_store_failed. |
Start a restore
POST /settings/backup/import/start
Checks the backup file and creates the restore job. It clears nothing yet; each item is cleared when its first batch runs.
Permission: manage_options (Permissions)
Body parameters: (multipart form when you send file, JSON otherwise)
| Name | Type | Required | Default | What it does |
|---|---|---|---|---|
items | array of strings | Yes | none | The items to restore, from the item list. Send them as items[] in a form. Unknown keys are dropped. The file must hold every item you choose. |
file | file | One of file or upload_id | none | The backup file, sent in one request. |
upload_id | string | One of file or upload_id | empty | The ID of a complete chunked upload made by the same user. When set, file is ignored. The plugin deletes the upload after it takes the file. |
Response: 200. data is the job payload with type set to import and status set to running.
bash
curl -X POST \
-u USER:APPLICATION_PASSWORD \
-F "[email protected]" \
-F "items[]=links" -F "items[]=categories" -F "items[]=tags" \
"https://example.com/wp-json/link-manager-pro/v1/settings/backup/import/start"json
{
"success": true,
"data": {
"job_id": "c3d8f0a2-61b4-4d97-a2e5-7f9014be3a18",
"type": "import",
"status": "running",
"total": 1234,
"processed": 0,
"percent": 0,
"current_item": "categories",
"items": ["categories", "tags", "links", "link_meta"],
"items_total": { "categories": 4, "tags": 7, "links": 1200, "link_meta": 23 },
"items_processed": { "categories": 0, "tags": 0, "links": 0, "link_meta": 0 },
"error": null
}
}Errors:
| Status | When |
|---|---|
400 | No valid item (backup_items_required). No file (backup_file_required) or a failed upload (backup_file_upload_failed, backup_file_upload_invalid). Not a .json name (backup_file_extension) or not JSON content (backup_file_mime). A bad schema_version (backup_file_invalid) or one newer than the plugin knows (backup_newer_format). A chosen item is missing or malformed in the file (backup_item_missing, backup_item_invalid). Posts from another site (backup_posts_foreign_site). Missing dependent items (backup_cross_site_dependants, with an items list in the error data). |
404 | The upload_id is unknown or expired (backup_upload_not_found). |
403 | The upload_id belongs to another user (backup_upload_forbidden). |
409 | A restore is already running (backup_import_running), or the upload is not complete (backup_upload_incomplete). |
500 | The file could not be stored (backup_file_store_failed). |
507 | The server has no disk space to prepare the files (backup_disk_full). |
Run a restore batch
POST /settings/backup/import/progress
Restores the next batch of rows and returns the new progress. Call it until status is done.
Permission: manage_options (Permissions)
Body parameters: (JSON)
| Name | Type | Required | Default | What it does |
|---|---|---|---|---|
job_id | string | Yes | none | The job_id from the start route. |
Response: 200. data is the job payload, plus busy: true when another request holds the restore. A job that is done or cancelled returns its payload again. A done job also holds message and, when something was skipped, notes.
json
{
"success": true,
"data": {
"job_id": "c3d8f0a2-61b4-4d97-a2e5-7f9014be3a18",
"type": "import",
"status": "done",
"total": 1234,
"processed": 1234,
"percent": 100,
"current_item": null,
"items": ["categories", "tags", "links", "link_meta"],
"items_total": { "categories": 4, "tags": 7, "links": 1200, "link_meta": 23 },
"items_processed": { "categories": 4, "tags": 7, "links": 1200, "link_meta": 23 },
"error": null,
"message": "Import completed successfully."
}
}Errors:
| Status | When |
|---|---|
400 | job_id is empty. The code is backup_job_required. |
404 | No job has that ID. The code is backup_job_not_found. |
500 | The restore failed and was rolled back (backup_import_failed). The message reads "Restore failed while restoring {item}: {cause} All data was rolled back; nothing was changed." The item is named as a readable label, such as "link rules", "link details", "Link Health records" or "posts and pages". When the run was past the last item, it names the last one, the job is already failed (backup_job_failed), or a count did not match (backup_restore_incomplete). Your previous data was put back. If the rollback itself could not finish, the message names the safety-copy tables the plugin kept. |
Get the running restore
GET /settings/backup/import/active
Returns the unfinished restore, so a screen that reloads can resume it.
Permission: manage_options (Permissions)
Query parameters: None.
Response: 200. data is the job payload of the running restore, or null when none is running.
json
{
"success": true,
"data": null
}Cancel a restore
POST /settings/backup/import/cancel
Stops a running restore and puts back every item it already cleared.
Permission: manage_options (Permissions)
Body parameters: (JSON)
| Name | Type | Required | Default | What it does |
|---|---|---|---|---|
job_id | string | Yes | none | The restore job ID. |
Response: 200. data is the job payload with status set to cancelled. error holds the plain sentence Restore cancelled. All data was rolled back; nothing was changed.
Errors:
| Status | When |
|---|---|
400 | job_id is empty. The code is backup_job_required. |
404 | No job has that ID. The code is backup_job_not_found. |
409 | The job is not running (backup_import_not_running), or another request is restoring a batch right now (backup_import_busy). Try again in a moment. |
500 | The rollback could not fully finish. The message names the safety-copy tables the plugin kept. |
Import from another plugin
Importing from Pretty Links, BetterLinks or ThirstyAffiliates is a job too. The source value is prettylinks, betterlinks or thirstyaff. The flow:
GET /settings/migration/detectlists the plugins whose data it found, with counts.POST /settings/migration/startcreates the job from asource, theitemsto bring over, aconflict_strategyand the affiliate domains to create.POST /settings/migration/processwith thejob_id, repeatedly. Each call runs one batch of one phase. Stop whenstatusisdone.GET /settings/migration/statusreads the job without running a batch.POST /settings/migration/cancelstops the job. Rows already imported stay.
Jobs run only through these routes or WP-CLI. There is no background mode.
The job payload that start, status, process and cancel return:
| Field | Type | What it holds |
|---|---|---|
job_id | string | The job ID (a UUID). |
status | string | running, done, failed or cancelled. |
current_phase | string or null | The phase running now, or null when all are done. |
phases | array of strings | The phases of this job, in order. They come from the items you chose. The last one is always finalize. |
phase_index | integer | The position of the current phase in phases, from 0. |
total | integer | Rows to import, summed over all items. |
processed | integer | Rows migrated, skipped or failed so far. |
percent | integer | processed as a percentage of total, 0 to 100. |
stats | object | Per item: total, migrated, skipped and failed, all integers. |
errors | array | Objects with item and message. |
warnings | array | Objects with item and message: what was imported differently or could not be carried over. Up to 200 are kept. |
more_warnings | integer | Warnings dropped after the 200. |
link_check | object or null | The result of the last step, which resolves every imported link. null until then. Described below. |
link_check holds checked, working, protected, switched_off and scheduled (all integers), problems (objects with link_id, slug and message, up to 100) and clashes (objects with link_id, slug, path, key, code, goes_to and fixable, up to 100).
The phases by source:
| Source | Items you can choose | Phases |
|---|---|---|
prettylinks | links, groups, categories, tags, clicks, settings, affiliates | groups, links, terms, clicks, settings, finalize |
betterlinks | links, categories, tags, clicks, passwords, keywords, geo_rules, settings, affiliates | terms, links, keywords, passwords, geo_rules, clicks, settings, finalize |
thirstyaff | links, categories, clicks, keywords, rules, settings, affiliates | terms, links, keywords, rules, clicks, settings, finalize |
A phase appears only when you chose an item for it. affiliates has no phase: finalize creates the affiliates for the domains you send.
Detect other plugins
GET /settings/migration/detect
Lists the plugins this site has data from. A plugin with no data is left out.
Permission: manage_options (Permissions)
Query parameters: None.
Response: 200. data.plugins is an array. Each entry holds:
| Field | Type | What it holds |
|---|---|---|
detected | boolean | Always true in this list. |
source | string | prettylinks, betterlinks or thirstyaff. |
name | string | The plugin name: Pretty Links, BetterLinks or ThirstyAffiliates. |
version | string | The installed version, or an empty string when it cannot be read. |
counts | object | Rows per item, as integers. Includes affiliates, the number of suggested new affiliate domains. |
items | array of strings | The items you can import from this source (the table above). |
affiliate_domains | array | Domains of the destinations, each with domain, links, clicks, signal (why it looks like an affiliate, or an empty string), suggested and existing (both boolean). Suggested domains come first. |
link_prefix | string | thirstyaff only. The short-link prefix ThirstyAffiliates used. |
affiliate_prefix_suggestion | string | thirstyaff only. An affiliate prefix to offer, or an empty string. It is empty when an affiliate prefix is already set or ThirstyAffiliates' prefix breaks a prefix rule. |
json
{
"success": true,
"data": {
"plugins": [
{
"detected": true,
"source": "prettylinks",
"name": "Pretty Links",
"version": "3.6.0",
"counts": { "links": 120, "clicks": 4800, "groups": 3, "categories": 2, "tags": 5, "affiliates": 4 },
"items": ["links", "groups", "categories", "tags", "clicks", "settings", "affiliates"],
"affiliate_domains": [
{ "domain": "shop.example.net", "links": 12, "clicks": 640, "signal": "sponsored", "suggested": true, "existing": false }
]
}
]
}
}Dismiss the import prompt
POST /settings/migration/dismiss
Remembers that you do not want the import offer for one source.
Permission: manage_options (Permissions)
Body parameters: (JSON)
| Name | Type | Required | Default | What it does |
|---|---|---|---|---|
source | string | Yes | none | The source to stop offering, for example prettylinks. The plugin does not check the value against the three sources. |
Response: 200. data is null.
Errors:
| Status | When |
|---|---|
400 | source is empty. The code is migration_source_required. |
Start an import job
POST /settings/migration/start
Creates the import job. It brings nothing over until you call process.
Permission: manage_options (Permissions)
Body parameters: (JSON)
| Name | Type | Required | Default | What it does |
|---|---|---|---|---|
source | string | Yes | none | prettylinks, betterlinks or thirstyaff. |
items | array of strings | Yes | none | The items to import, from the source's list above. Other values are dropped. |
conflict_strategy | string | No | skip | A source link with the same slug and destination as a link here is the same link. skip leaves that link as it is and only adds the source's clicks and old address to it. overwrite replaces its settings but never its click history or its auto-linking settings. A link with the same slug and a different destination is imported as a new link. Another value counts as skip. |
affiliate_domains | array of strings | No | [] | The domains to create as affiliates. Characters other than letters, digits, dots and hyphens are removed, and the rest is lowercased. Used only when items includes affiliates. |
Response: 200. data is the import job payload with status set to running.
bash
curl -X POST \
-u USER:APPLICATION_PASSWORD \
-H "Content-Type: application/json" \
-d '{"source":"prettylinks","items":["links","categories","clicks","affiliates"],"conflict_strategy":"skip","affiliate_domains":["shop.example.net"]}' \
"https://example.com/wp-json/link-manager-pro/v1/settings/migration/start"json
{
"success": true,
"data": {
"job_id": "e7a1b3c5-2d4f-4a68-b9c0-1f2e3d4c5b6a",
"status": "running",
"current_phase": "links",
"phases": ["links", "terms", "clicks", "finalize"],
"phase_index": 0,
"total": 4920,
"processed": 0,
"percent": 0,
"stats": {
"links": { "total": 120, "migrated": 0, "skipped": 0, "failed": 0 },
"categories": { "total": 2, "migrated": 0, "skipped": 0, "failed": 0 },
"clicks": { "total": 4800, "migrated": 0, "skipped": 0, "failed": 0 },
"affiliates": { "total": 1, "migrated": 0, "skipped": 0, "failed": 0 }
},
"errors": [],
"warnings": [],
"more_warnings": 0,
"link_check": null
}
}Errors:
| Status | When |
|---|---|
400 | source is not one of the three (migration_source_invalid), items is empty or not an array (migration_items_required), or no item is valid for the source (migration_start_failed). |
Run an import batch
POST /settings/migration/process
Runs the next batch of the job and returns the new progress. Call it until status is done.
Permission: manage_options (Permissions)
Body parameters: (JSON)
| Name | Type | Required | Default | What it does |
|---|---|---|---|---|
job_id | string | Yes | none | The job_id from the start route. |
Response: 200. data is the import job payload. Two extras can appear:
| Field | Type | What it holds |
|---|---|---|
busy | boolean | true when another request is running a batch of this job. The payload is the current state. Call again. |
affiliate_prefix_offer | string | Only when status is done. A ThirstyAffiliates import with no affiliate prefix set returns that plugin's prefix here, when it passes the prefix rules. Otherwise an empty string. The prefix is the Affiliate link prefix (General settings). |
A finished job also stops the import prompt for its source.
Errors:
| Status | When |
|---|---|
400 | job_id is empty (migration_job_required), or the job is not running, for example cancelled or failed (migration_process_failed). |
404 | No job has that ID. The code is migration_job_not_found. |
A batch that throws an error sets status to failed and puts the message in errors. The route still answers 200.
Get an import job
GET /settings/migration/status
Reads the job's progress without running a batch.
Permission: manage_options (Permissions)
Query parameters:
| Name | Type | Required | Default | What it does |
|---|---|---|---|---|
job_id | string | Yes | none | The import job ID. |
Response: 200. data is the import job payload plus affiliate_prefix_offer (a string, empty unless the job is done and the offer rules above apply).
Errors:
| Status | When |
|---|---|
400 | job_id is empty. The code is migration_job_required. |
404 | No job has that ID. The code is migration_job_not_found. |
Cancel an import job
POST /settings/migration/cancel
Stops the job. Rows it already imported stay in place.
Permission: manage_options (Permissions)
Body parameters: (JSON)
| Name | Type | Required | Default | What it does |
|---|---|---|---|---|
job_id | string | Yes | none | The import job ID. |
Response: 200. data is the import job payload with status set to cancelled.
Errors:
| Status | When |
|---|---|
400 | job_id is empty (migration_job_required), or the job could not be stopped (migration_cancel_failed). |
404 | No job has that ID. The code is migration_job_not_found. |
Replace old shortcodes
After an import, posts can still hold the old plugin's shortcodes: [prettylink], [thirstylink] and the <ta> tag. These three routes find them and swap each one for a normal link to the imported short link. The flow is summary, then scan, then replace. Scan and replace work through the posts in pages by post ID. Pass the next value of one answer as after in the next call, until next is null.
A shortcode whose link was not imported is left as it is and listed as unresolved. Shortcodes stored in page-builder data are only counted, and you replace them by hand.
Count the old shortcodes
GET /settings/migration/shortcodes
Counts the posts that still hold an imported plugin's shortcodes.
Permission: manage_options (Permissions)
Query parameters: None.
Response: 200. data holds:
| Field | Type | What it holds |
|---|---|---|
posts | integer | Posts with a shortcode in their content. |
builder_posts | integer | Posts with a shortcode in page-builder data. |
imported | boolean | true when any short link was imported from one of the three plugins. |
The two counts are cached for one hour.
json
{
"success": true,
"data": { "posts": 14, "builder_posts": 2, "imported": true }
}Review the shortcode changes
GET /settings/migration/shortcodes/scan
Lists the next 100 posts that would change, and what would change in each. It saves nothing.
Permission: manage_options (Permissions)
Query parameters:
| Name | Type | Required | Default | What it does |
|---|---|---|---|---|
after | integer | No | 0 | Scan posts with an ID above this. Use the next of the last answer. |
Response: 200. data holds:
| Field | Type | What it holds |
|---|---|---|
posts | array | One object per post that would change or holds an unresolved shortcode. Fields below. |
next | integer or null | The post ID to pass as after, or null at the end. |
builder | object | Only when after is 0. Posts with shortcodes in page-builder data: posts (up to 20, each with the post fields id, title, type, status, edit_url, view_url), more (boolean) and total (integer). |
Each entry in posts holds the post fields id (integer), title, type (the post type's name), status (the status label), edit_url and view_url (strings), and:
| Field | Type | What it holds |
|---|---|---|
replaceable | integer | Shortcodes in this post that would be replaced. |
blocked | string | A reason a save would be skipped (no permission, markup WordPress would strip, a scheduled post that is due). An empty string when it can be saved. |
unresolved | array of strings | Up to 10 shortcodes whose link was not imported, cut to 160 characters. |
example | object or null | before and after, the first replacement found, each cut to 160 characters. |
sources | array of strings | The sources found, prettylinks or thirstyaff. |
Replace the shortcodes
POST /settings/migration/shortcodes/replace
Replaces the shortcodes in the next 25 posts and saves them. Each post gets a revision first.
Permission: manage_options (Permissions)
Body parameters: (JSON)
| Name | Type | Required | Default | What it does |
|---|---|---|---|---|
after | integer | No | 0 | Process posts with an ID above this. Use the next of the last answer. |
Response: 200. data holds:
| Field | Type | What it holds |
|---|---|---|
replaced | integer | Shortcodes replaced in saved posts. |
posts_changed | integer | Posts saved. |
unresolved | integer | Shortcodes left because their link was not imported. |
skipped | array | Posts that were not saved. Each holds the post fields (id, title, type, status, edit_url, view_url), reason (a sentence) and shortcodes (integer). |
next | integer or null | The post ID to pass as after, or null at the end. |
json
{
"success": true,
"data": {
"replaced": 31,
"posts_changed": 12,
"unresolved": 0,
"skipped": [],
"next": null
}
}Errors:
| Status | When |
|---|---|
409 | A replacement is already running in another window. The code is shortcode_replace_busy. The lock lasts up to one minute. |