Skip to content

Settings endpoints ​

Use these REST routes to read and save each settings group, test the AI connection, import SEO plugin redirects and manage the country database. Each entry lists the permission, parameters, response fields and errors.

Sign-in and the response envelope are in the REST API overview.

All paths are relative to /wp-json/link-manager-pro/v1. The examples use https://example.com and an Application Password placeholder (USER:APPLICATION_PASSWORD). Inside WordPress, send the X-WP-Nonce header instead. Every route here needs manage_options unless its entry says otherwise (Permissions). The backup routes and the import from other plugins are on Backup and import endpoints.

How settings requests work ​

Every settings group follows the same rules. Each group entry below lists only what is specific to it.

Reading. GET /settings/{group} returns the group as one flat JSON object in data: every key of the group with its saved value, or the default when nothing is saved. Keys that are not in the schema are never returned. The Default column is the schema default. A site that never had the plugin starts with three other saved values: anonymize_ips is "yes", params_on_site_redirects is "no" and health_scan_schedule is "weekly". Each is set once, and only for a group that was never saved.

Saving. POST /settings/{group} takes a JSON object of keys to change. Form-encoded fields work too.

  • Partial updates: keys you leave out keep their saved value. You can send one key.
  • Unknown keys: keys that are not in the group are dropped without an error. An unknown group is not a route.
  • Sanitizing: every value is sanitized by its type. A number is cast to an integer and clamped to its range, not rejected. A select value outside the allowed list becomes the key's default. A "yes"/"no" key given anything else becomes the key's default. A list or object sent for a single-value key is ignored and the saved value stays. Text keys go through sanitize_text_field, and textarea keys through sanitize_textarea_field.
  • Empty body: a body with no fields saves nothing and still answers 200.
  • Response: a save answers 200 with data set to null, except POST /settings/general, which returns warnings.
  • Hook: after a save, the plugin runs the action link_manager_pro_settings_updated with the group name, the new values and the old values, secrets masked as described under Secrets below (Action hooks). Plugins can also add keys to the schema with the link_manager_pro_settings_schema filter, so a site can return more keys than the tables below.

Booleans are the strings "yes" and "no", never true or false. Key names are snake_case.

Secrets. A key stored encrypted is returned masked, never in clear. Only the AI api_key is one. The masked form is lmpro_masked: followed by the last four characters of the key (the whole value when it is shorter than four), or an empty string when no key is saved. When you save a value that starts with lmpro_masked:, the plugin ignores it and keeps the saved secret. So you can send a group back as you read it without overwriting the key.

Errors. A failed validation answers 422 with message and an errors object of messages per field (REST API overview). POST /settings/general, POST /settings/advanced and POST /settings/tracking can fail this way. A request without manage_options gets 401 or 403.

General settings ​

The short link prefixes, the slug length and how links are masked.

Get the general settings ​

GET /settings/general

Returns the general settings.

Permission: manage_options

Query parameters: None.

Response: 200. data holds these keys:

KeyTypeDefaultAllowed values or rangeWhat it does
auto_mask_on_savestring"yes""yes", "no"Masks links in the content WordPress is about to save.
link_prefixstring"link"A slug: lowercase letters, numbers, hyphensThe path in front of a short link's slug.
internal_prefixstring"go"A slugThe path used for short links that point to this site.
affiliate_prefixstring""A slug, or emptyAn optional path for short links whose destination is on one of your affiliates' domains, for example /recommends/amazon-deal. Empty means they follow the other rules: internal_prefix for your own site when mask_internal_links is on, else link_prefix. A redirect's hidden link never uses it.
code_lengthinteger86 to 16The length of automatically generated slugs.
mask_internal_linksstring"no""yes", "no"Also masks links that point to this site. Those short links use internal_prefix.
json
{
  "success": true,
  "data": {
    "auto_mask_on_save": "yes",
    "link_prefix": "link",
    "internal_prefix": "go",
    "affiliate_prefix": "",
    "code_length": 8,
    "mask_internal_links": "no"
  }
}

Save the general settings ​

POST /settings/general

Saves the general settings and checks the three prefixes first.

Permission: manage_options

Body parameters: any of the keys in the table above, as JSON. See How settings requests work.

Prefix rules. When the body holds at least one prefix, the plugin checks all three prefixes as they would be after the save. A prefix you did not change is not checked again, so an older value never blocks another change. A broken rule answers 422 and nothing is saved. The rules:

  • A prefix must be a slug: lowercase letters, numbers and hyphens. Surrounding slashes and spaces are removed and capitals are lowercased before the check.
  • A prefix cannot be a path WordPress answers itself: wp-admin, wp-content, wp-includes, wp-json, feed, embed, page, comments, search, author, category, tag or trackback.
  • The three prefixes must differ from each other.
  • link_prefix and internal_prefix cannot be empty. affiliate_prefix can.

Side effects. When a prefix changes, the plugin remembers the old prefix so old short links keep working, clears the auto-link cache and flushes the rewrite rules. The old prefix keeps its full place for 90 days. After that it still opens its short link wherever WordPress has nothing at that address. The plugin remembers the last 5 replaced prefixes. Every prefix you own, and a remembered one, opens every short link. See Update links after a prefix change for the content update.

Response: 200. data holds:

FieldTypeWhat it holds
warningsobjectMessages that do not block the save, keyed by prefix name (link_prefix, internal_prefix, affiliate_prefix). An empty list ([]) when there are none.

A warning appears when a published page or post sits at that path, or when ThirstyAffiliates is active and uses that prefix.

bash
curl -X POST -u USER:APPLICATION_PASSWORD \
  -H "Content-Type: application/json" \
  -d '{"link_prefix":"go-to","code_length":10}' \
  "https://example.com/wp-json/link-manager-pro/v1/settings/general"
json
{
  "success": true,
  "data": { "warnings": [] }
}

Errors:

StatusWhen
422A prefix breaks a rule above. errors is keyed by the prefix name, for example link_prefix.

How Link Health, traffic monitoring, the link audit, internal link optimization and link equity behave.

GET /settings/link-intelligence

Returns the Link Intelligence settings.

Permission: manage_options

Query parameters: None.

Response: 200. data holds these keys:

KeyTypeDefaultAllowed values or rangeWhat it does
health_scan_schedulestring"disabled""disabled", "daily", "weekly", "monthly"How often the link health scan runs.
health_post_typesstring"post,page"Comma-separated post type namesThe content types Link Health checks.
traffic_monitor_modestring"sitemap""sitemap", "all"Which visitor 404s the traffic monitor records.
traffic_retention_daysinteger00 to 3650Days a visitor 404 row is kept after it was last seen. 0 keeps it.
audit_min_inboundinteger31 to 20The inbound internal links the link audit expects a post to have at least.
audit_min_outboundinteger21 to 20The outbound internal links the link audit expects a post to have at least.
optimization_enabledstring"yes""yes", "no"Turns the internal link optimization on or off.
optimization_chars_per_linkinteger50050 to 5000Characters of content per recommended internal link.
optimization_deltainteger10 to 20How far the internal link count may differ from the recommended count before it counts as low or high.
equity_budgetinteger1001 to 10000The equity budget used in the link equity calculation.
equity_position_penaltyinteger500 to 100The position penalty, in percent, used in the link equity calculation.
json
{
  "success": true,
  "data": {
    "health_scan_schedule": "weekly",
    "health_post_types": "post,page",
    "traffic_monitor_mode": "sitemap",
    "traffic_retention_days": 0,
    "audit_min_inbound": 3,
    "audit_min_outbound": 2,
    "optimization_enabled": "yes",
    "optimization_chars_per_link": 500,
    "optimization_delta": 1,
    "equity_budget": 100,
    "equity_position_penalty": 50
  }
}

POST /settings/link-intelligence

Saves the Link Intelligence settings and clears the cached Link Health counts, because the chosen content types change what they include.

Permission: manage_options

Body parameters: any of the keys in the table above. See How settings requests work.

Response: 200, data is null.

Notification settings ​

The email digest.

Get the notification settings ​

GET /settings/notifications

Returns the notification settings.

Permission: manage_options

Query parameters: None.

Response: 200. data holds these keys:

KeyTypeDefaultAllowed values or rangeWhat it does
digest_frequencystring"disabled""disabled", "daily", "weekly", "monthly"How often the email digest is sent.
digest_recipientstring""Email addresses separated by commas, semicolons or spacesWho receives the digest.

On save, each address is sanitized and checked, invalid ones are dropped, duplicates are removed, and the rest are stored as one comma-separated list ([email protected], [email protected]).

Save the notification settings ​

POST /settings/notifications

Saves the notification settings.

Permission: manage_options

Body parameters: any of the keys in the table above. See How settings requests work.

Response: 200, data is null.

Advanced settings ​

Access, report caching, proxy headers and what happens on uninstall.

Get the advanced settings ​

GET /settings/advanced

Returns the advanced settings.

Permission: manage_options

Query parameters: None.

Response: 200. data holds these keys:

KeyTypeDefaultAllowed values or rangeWhat it does
admin_capabilitystring"manage_options""manage_options", "manage_link_manager_pro"Who can use the plugin besides administrators. See Access for editors.
report_cache_ttlinteger6000 to 86400Seconds a report is cached.
trust_proxy_headersstring"no""yes", "no"Reads the visitor IP from proxy headers.
trusted_proxiesstring""IP addresses or CIDR ranges, separated by new lines or commasThe proxies whose forwarded headers are trusted for the visitor IP and country. Passed through the link_manager_pro_trusted_proxies filter.
delete_data_on_uninstallstring"no""yes", "no"Deletes the plugin's data when the plugin is deleted.

Save the advanced settings ​

POST /settings/advanced

Saves the advanced settings.

Permission: manage_options

Body parameters: any of the keys in the table above. See How settings requests work.

Response: 200, data is null.

Errors: 422 with errors.trusted_proxies when a line in trusted_proxies is not an IP address or CIDR range. The message names up to 10 such lines. Nothing is saved.

Integration settings ​

Caching, consent and SEO plugin behavior.

Get the integration settings ​

GET /settings/integrations

Returns the integration settings.

Permission: manage_options

Query parameters: None.

Response: 200. data holds these keys:

KeyTypeDefaultAllowed values or rangeWhat it does
clear_cache_on_changestring"yes""yes", "no"Clears a detected page cache when a short link or redirect changes.
seo_404_cleanupstring"manual""automatic", "manual"Whether saving an active redirect also clears its entry from the Rank Math 404 log by itself.
respect_consentstring"yes""yes", "no"Waits for visitor consent from a detected consent plugin before tracking.
consent_categorystring"statistics""statistics", "marketing", "any"The consent category that allows tracking. "any" accepts either of the other two.

Save the integration settings ​

POST /settings/integrations

Saves the integration settings.

Permission: manage_options

Body parameters: any of the keys in the table above. See How settings requests work.

Response: 200, data is null.

Redirection settings ​

The default redirect type, extra parameters and rel attributes.

Get the redirection settings ​

GET /settings/redirection

Returns the redirection settings.

Permission: manage_options

Query parameters: None.

Response: 200. data holds these keys:

KeyTypeDefaultAllowed values or rangeWhat it does
global_paramsstring""A query string, such as ref=newsletter&src=siteParameters added to every short link redirect. Read with PHP parse_str(). The plugin rebuilds the value on save: it decodes each name and value, strips tags and encodes them again, so + becomes %20. A double-encoded value such as %2541 is kept as %2541, and the redirect sends it as is. A redirect of an old address to a page on your own site gets them only while params_on_site_redirects is "yes".
params_on_site_redirectsstring"yes""yes", "no"Adds the site-wide parameters to Simple Redirects whose target is this site.
redirect_typeinteger301301, 302, 307The default redirect status for new short links. Any other value becomes 301.
rel_rulesobjectsee belowKeys nofollow, noopener, noreferrer, sponsoredWhich rel values the plugin adds to links.

rel_rules holds one object per rel value, each with two fields: global ("yes" or "no", default "no") and domains (a textarea string, default ""). The plugin normalizes each listed line and the link's host when it matches them, so https://WWW.example.com/ matches example.com. Saved text is not rewritten. The defaults are:

json
{
  "nofollow":   { "global": "yes", "domains": "" },
  "noopener":   { "global": "yes", "domains": "" },
  "noreferrer": { "global": "no",  "domains": "" },
  "sponsored":  { "global": "no",  "domains": "" }
}

Save the redirection settings ​

POST /settings/redirection

Saves the redirection settings.

Permission: manage_options

Body parameters: any of the keys in the table above. See How settings requests work.

Send rel_rules whole. The plugin replaces the stored object with the entries you send, so a rel value you leave out is no longer saved. Names other than the four above are dropped, and an entry that is not an object is skipped.

Response: 200, data is null.

Tracking settings ​

Click tracking, bot filtering and IP handling.

Get the tracking settings ​

GET /settings/tracking

Returns the tracking settings.

Permission: manage_options

Query parameters: None.

Response: 200. data holds these keys:

KeyTypeDefaultAllowed values or rangeWhat it does
track_clicksstring"yes""yes", "no"Records clicks on short links.
click_retention_daysinteger00 to 3650Days a click is kept. 0 keeps clicks.
blocked_ipsstring""One IP address per line, exact matchClicks from these addresses are not counted.
enable_bot_detectionstring"yes""yes", "no"Leaves known bots out of the click data.
blocked_bot_patternsstring""One text per line, matched anywhere in the user agent, ignoring caseYour own bot list. Any entry replaces the 48 built-in patterns; it doesn't add to them. Used only while enable_bot_detection is "yes".
anonymize_ipsstring"no""yes", "no"Anonymizes stored IP addresses.
track_internal_clicksstring"no""yes", "no"Records clicks on short links inside your own pages.
internal_click_selectorstring".entry-content"Free textThe CSS selector for the area where internal clicks are tracked. A save over REST refuses a selector browsers always reject, with a 422 and errors.internal_click_selector (only while track_internal_clicks is "yes" and the value changed). WP-CLI, imports and restores store .entry-content instead. Some bad selectors still get through, and the tracker then falls back to .entry-content.

Save the tracking settings ​

POST /settings/tracking

Saves the tracking settings. The plugin also records when tracking was changed, so report health verdicts that depend on tracking wait before they judge a link.

Permission: manage_options

Body parameters: any of the keys in the table above. See How settings requests work.

Response: 200, data is null.

Errors: 422 with errors.internal_click_selector ("Enter a valid CSS selector.") when the selector is one browsers always reject. Nothing is saved.

Automatic keyword links in post content.

GET /settings/autolink

Returns the auto-link settings.

Permission: manage_options

Query parameters: None.

Response: 200. data holds these keys:

KeyTypeDefaultAllowed values or rangeWhat it does
autolink_enabledstring"yes""yes", "no"Turns auto-linking on or off.
autolink_max_per_keywordinteger11 to 50The most links one keyword gets in a post.
autolink_max_per_pageinteger00 to 500The most auto-links in a post. 0 means no limit.
autolink_max_per_targetinteger00 to 500The most links to the same destination (host and path, query kept) in a post. 0 means no limit.
autolink_case_sensitivestring"no""yes", "no"Matches keywords with the same capital letters only.
autolink_exclude_headingsstring"yes""yes", "no"Skips headings.
autolink_post_typesstring"post,page"Comma-separated post type namesThe content types that get auto-links.

POST /settings/autolink

Saves the auto-link settings and clears the auto-link cache.

Permission: manage_options

Body parameters: any of the keys in the table above. See How settings requests work.

Response: 200, data is null.

Disclosure settings ​

The affiliate disclosure text added to posts.

Get the disclosure settings ​

GET /settings/disclosure

Returns the disclosure settings.

Permission: manage_options

Query parameters: None.

Response: 200. data holds these keys:

KeyTypeDefaultAllowed values or rangeWhat it does
disclosure_enabledstring"no""yes", "no"Adds the disclosure to posts.
cookie_consent_acknowledgedstring"no""yes", "no"Records that the site owner has seen the cookie consent note.
disclosure_textstring"Disclosure: This post contains affiliate links. If you purchase through these links, I may earn a commission at no additional cost to you."Free text (textarea)The disclosure text.
disclosure_positionstring"before""before", "after"Whether the disclosure comes before or after the content.
disclosure_stylestring"boxed""minimal", "boxed", "custom"The look of the disclosure.
disclosure_custom_classstring""Free textThe CSS class used with the "custom" style.
disclosure_post_typesstring"post,page"Comma-separated post type namesThe content types that get the disclosure.

Save the disclosure settings ​

POST /settings/disclosure

Saves the disclosure settings.

Permission: manage_options

Body parameters: any of the keys in the table above. See How settings requests work.

Response: 200, data is null.

AI settings ​

The provider, model and limits for the AI features. The key is a secret, so it comes back masked.

Get the AI settings ​

GET /settings/ai

Returns the AI settings, the model lists for the settings screen and the default model per provider.

Permission: manage_options

Query parameters: None.

Response: 200. data holds these keys:

KeyTypeDefaultAllowed values or rangeWhat it does
sourcestring"auto""auto", "wordpress", "custom"Where requests go: the AI connection set up in WordPress, your own key, or the first one available.
providerstring"none""none", "openai", "gemini", "anthropic"The provider used with your own key.
api_keystring""Free textYour provider key. Returned masked, see below.
modelstring""Up to 120 charactersThe model name. Empty uses the provider's default. Longer text is cut to 120 characters.
max_tokensinteger2000256 to 32000The most tokens one request may use.
enable_suggestionsstring"no""yes", "no"Turns the AI link suggestions on.
enable_untracked_detectionstring"yes""yes", "no"Turns the detection of untracked links on.
rate_limit_maxinteger300 to 100000Requests one user may make in the window. 0 turns the limit off.
rate_limit_windowinteger3001 to 86400The window length in seconds.
_modelsobjectnoneRead-onlyModel choices per provider (openai, gemini, anthropic), each a list of { "value", "label" }.
_model_defaultsobjectnoneRead-onlyThe default model name per provider. Today openai is gpt-4o-mini, gemini is gemini-2.5-flash and anthropic is claude-haiku-4-5-20251001.

The two _-prefixed keys are added to the response only. Sending them in a save does nothing.

json
{
  "success": true,
  "data": {
    "source": "custom",
    "provider": "openai",
    "api_key": "lmpro_masked:a1b2",
    "model": "",
    "max_tokens": 2000,
    "enable_suggestions": "yes",
    "enable_untracked_detection": "yes",
    "rate_limit_max": 30,
    "rate_limit_window": 300,
    "_models": {
      "openai": [{ "value": "gpt-4o-mini", "label": "GPT-4o mini" }],
      "gemini": [{ "value": "gemini-2.5-flash", "label": "Gemini 2.5 Flash" }],
      "anthropic": [{ "value": "claude-haiku-4-5-20251001", "label": "Claude Haiku 4.5" }]
    },
    "_model_defaults": {
      "openai": "gpt-4o-mini",
      "gemini": "gemini-2.5-flash",
      "anthropic": "claude-haiku-4-5-20251001"
    }
  }
}

Save the AI settings ​

POST /settings/ai

Saves the AI settings. Send api_key with a new key to replace it. Send the masked value you read, or leave api_key out, to keep the saved key. Sending an empty string clears the key. When a request changes provider and has no model, a saved model that the new provider doesn't list is reset to empty, which means the provider's default. A model sent in the same request is kept as sent.

Permission: manage_options

Body parameters: any of the keys in the table above, except the _ keys. See How settings requests work.

Response: 200, data is null.

Test the AI connection ​

POST /settings/ai/test

Checks which AI connection the plugin would use. With your own key (source resolves to custom), it also sends a one-word test request to the provider.

Permission: manage_options

Body parameters: None. The route tests the saved settings.

Response: 200. data holds:

FieldTypeWhat it holds
configuredbooleantrue when a connection is available.
sourcestring or null"custom" for your own key, "wordpress" for the WordPress connection. null when nothing is configured.
providerstring or nullThe provider name. "site-connector" for the WordPress connection.
modelstring or nullThe model name. "site-default" for the WordPress connection with no model set.
livebooleanWhether the AI assistant is available on this site.
live_notestringA sentence that explains live when it is false. Empty otherwise.
test_okbooleantrue when the test request answered. Only present when source is "custom".
test_errorstringThe failure text, empty when the test worked. Only present with test_ok.

No test request is sent for the WordPress connection or when nothing is configured, so test_ok and test_error are missing in those cases.

json
{
  "success": true,
  "data": {
    "configured": true,
    "source": "custom",
    "provider": "openai",
    "model": "gpt-4o-mini",
    "live": true,
    "live_note": "",
    "test_ok": true,
    "test_error": ""
  }
}

Errors:

StatusWhen
429More than 10 tests by this user in 5 minutes. The code is lmp_ai_rate_limited. The test sends a billed request, so it is limited more tightly than the AI features.

Integrations and post types ​

Two read-only routes that fill the Integrations and settings screens.

Get the detected integrations ​

GET /settings/integrations/status

Returns which cache, consent and automation plugins the plugin sees, and whether a country database is available.

Permission: manage_options

Query parameters: None.

Response: 200. data holds:

FieldTypeWhat it holds
cachingarray of stringsNames of the active page cache plugins: WP Rocket, LiteSpeed Cache, W3 Total Cache, WP Super Cache, WP Fastest Cache. Empty when none.
automatorwpbooleantrue when AutomatorWP is active.
consentarray of stringsZero or one name: the consent plugin whose consent the plugin reads. Empty when none.
consent_supportedarray of stringsEvery consent plugin the plugin can read: Complianz, CookieYes, Cookiebot.
geoipbooleantrue when a country database file can be read.
json
{
  "success": true,
  "data": {
    "caching": ["WP Rocket"],
    "automatorwp": false,
    "consent": [],
    "consent_supported": ["Complianz", "CookieYes", "Cookiebot"],
    "geoip": true
  }
}

List the public post types ​

GET /settings/post-types

Returns the public post types a site can pick for Link Health, auto-links and the disclosure.

Permission: manage_options

Query parameters: None.

Response: 200. data is a list. Each item holds:

FieldTypeWhat it holds
namestringThe post type name, for example post.
labelstringThe plural label, for example Posts.

Attachments and the block editor types wp_navigation, wp_block, wp_template, wp_template_part and wp_global_styles are left out.

json
{
  "success": true,
  "data": [
    { "name": "post", "label": "Posts" },
    { "name": "page", "label": "Pages" }
  ]
}

Get the import prompt ​

GET /settings/migration/prompt

Returns whether the dashboard should offer to move data from Pretty Links, BetterLinks or ThirstyAffiliates.

Permission: link_manager_pro_access (Permissions)

Query parameters: None.

Response: 200. data holds:

FieldTypeWhat it holds
showbooleantrue when a plugin was detected that was not dismissed, migrated or already attempted.
sourcesarrayOnly when show is true. One item per detected plugin, fields below.
total_recordsintegerOnly when show is true. All counts of all listed sources added together.
recommendationobjectOnly when show is true. method is "cli" from 10000 records, else "ui". reason is "large_dataset" or "standard". message is a sentence.

Each item of sources holds:

FieldTypeWhat it holds
sourcestringprettylinks, betterlinks or thirstyaff.
namestringThe plugin name.
versionstringThe detected version, empty if unknown.
countsobjectInteger counts per record kind, for example links, categories, tags, clicks. The keys depend on the plugin.
cli_commandstringThe WP-CLI command for this source, wp link-manager-pro import --source={source}.
json
{
  "success": true,
  "data": { "show": false }
}

SEO plugin redirect import ​

These routes copy redirects from another SEO plugin into the plugin's Simple Redirects. Rank Math (rankmath) and SEOPress (seopress) can be imported. Yoast SEO (yoast) and All in One SEO (aioseo) are only detected, so their analyze and import calls answer 400.

Only exact-path rules with a 301, 302 or 307 status and a target are imported. Other rules are skipped with a reason. The import creates each redirect the way the Simple Redirects screen does, so its checks apply. A source that already exists in any state is skipped, so running the import again is safe. analyze reports it as already_imported and counts it under skipped, not importable.

Flow: call status, call analyze for a dry run, then call import again and again, each time with the offset set to the previous processed value, until done is true. The plugin looks at no more than 5000 rules per call.

List the detected SEO plugins ​

GET /settings/integrations/seo/status

Returns the active SEO plugins and whether Rank Math's 404 log can be cleaned.

Permission: manage_options

Query parameters: None.

Response: 200. data holds:

FieldTypeWhat it holds
pluginsarrayOne item per active SEO plugin that the plugin knows. Inactive or missing plugins are not listed.
reconcile_404_availablebooleantrue when Rank Math's 404 Monitor module is on and its table is present.

Each item of plugins holds:

FieldTypeWhat it holds
slugstringrankmath, seopress, yoast or aioseo. This is the {source} value.
namestringRank Math, SEOPress, Yoast SEO or All in One SEO.
activebooleanAlways true in this list.
store_availablebooleantrue when the plugin's redirect store exists. Always false for Yoast SEO and All in One SEO.
import_supportedbooleanfalse for Yoast SEO and All in One SEO.
versionstringThe plugin version, empty if unknown.
redirect_countintegerRules in the plugin's store. 0 when the store is missing or import is not supported.
json
{
  "success": true,
  "data": {
    "plugins": [
      {
        "slug": "rankmath",
        "name": "Rank Math",
        "active": true,
        "store_available": true,
        "import_supported": true,
        "version": "1.0.250",
        "redirect_count": 12
      }
    ],
    "reconcile_404_available": true
  }
}

Analyze an SEO plugin's redirects ​

GET /settings/integrations/seo/{source}/analyze

Counts how many redirects can be imported and why the others cannot. Nothing is written.

Permission: manage_options

Path parameters:

NameTypeWhat it is
sourcestringrankmath or seopress.

Query parameters: None.

Response: 200. data holds:

FieldTypeWhat it holds
totalintegerRules examined.
importableintegerRules that map to a Simple Redirect.
skippedintegerRules that do not.
reasonsobjectSkip reason to count. The reasons are unsupported_match (not an exact match), empty_source, unsupported_type (not 301, 302 or 307), no_target and trashed (a rule in the SEO plugin's trash), plus already_imported for a source that already exists as a redirect. An empty list ([]) when nothing is skipped.
samplearrayUp to 6 importable rules. Each item has source (string, absolute URL), target (string, absolute URL), method (integer, 301, 302 or 307) and status (string, active or inactive).
truncatedbooleantrue when the plugin holds more than 5000 rules and the rest were not examined.
json
{
  "success": true,
  "data": {
    "total": 12,
    "importable": 10,
    "skipped": 2,
    "reasons": { "unsupported_match": 2 },
    "sample": [
      {
        "source": "https://example.com/old-page",
        "target": "https://example.com/new-page",
        "method": 301,
        "status": "active"
      }
    ],
    "truncated": false
  }
}

Errors:

StatusWhen
400The code is seo_source_invalid when source is not a known plugin. The code is seo_store_unavailable when the plugin is not active, has no redirect store, or cannot be imported.

Import an SEO plugin's redirects ​

POST /settings/integrations/seo/{source}/import

Imports one batch of redirects as Simple Redirects.

Permission: manage_options

Path parameters:

NameTypeWhat it is
sourcestringrankmath or seopress.

Body parameters:

NameTypeRequiredDefaultWhat it does
offsetintegerNo0The first rule of the batch. A negative value counts as 0.
limitintegerNo100Rules in the batch. Kept between 1 and 500.
clean_404booleanNofalseAlso removes the matching entries from the Rank Math 404 log, for active redirects only. Does nothing without Rank Math's 404 Monitor.

Response: 200. data holds:

FieldTypeWhat it holds
importedintegerRedirects created in this batch.
skippedintegerRules not imported: a skip reason, a source that already exists, or a rejected redirect.
failedintegerRules that raised an unexpected error.
processedintegerRules handled so far, counting from the first. Use it as the next offset.
totalintegerRules examined in all.
donebooleantrue when processed has reached total.
messagesarray of stringsUp to 25 messages for rejected or failed rules, each starting with the source URL.
cleared_404integerRank Math 404 log rows removed. 0 when clean_404 was not set.
bash
curl -X POST -u USER:APPLICATION_PASSWORD \
  -H "Content-Type: application/json" \
  -d '{"offset":0,"limit":100,"clean_404":false}' \
  "https://example.com/wp-json/link-manager-pro/v1/settings/integrations/seo/rankmath/import"
json
{
  "success": true,
  "data": {
    "imported": 10,
    "skipped": 2,
    "failed": 0,
    "processed": 12,
    "total": 12,
    "done": true,
    "messages": [],
    "cleared_404": 0
  }
}

Errors: the same two 400 errors as the analyze route.

Country database ​

The country database is a file the plugin downloads once. It lets the plugin report the country of a click. The file is stored in the uploads folder under link-manager-pro/geoip, and the folder is protected from direct access.

Get the country database status ​

GET /settings/geoip/status

Returns whether the database file is installed and when it was downloaded.

Permission: manage_options

Query parameters: None.

Response: 200. data is the status object:

FieldTypeWhat it holds
installedbooleantrue when the downloaded file can be read.
sizeintegerThe file size in bytes. 0 when not installed.
sha256stringThe file checksum. Empty when not installed.
downloaded_atstringThe download time in UTC, YYYY-MM-DD HH:MM:SS. Empty when not installed.
sourcestringThe download address.
source_labelstringThe name of the data source.
php_supportedbooleantrue on PHP 8.1 or later, which the database reader needs.
downloadingbooleantrue while a download holds its lock (up to 5 minutes).
json
{
  "success": true,
  "data": {
    "installed": true,
    "size": 5242880,
    "sha256": "9f2c0e5a4b1d8c7e6f3a2b1c0d9e8f7a6b5c4d3e2f1a0b9c8d7e6f5a4b3c2d1e",
    "downloaded_at": "2026-10-20 08:30:00",
    "source": "https://cdn.jsdelivr.net/npm/@ip-location-db/[email protected]/geo-whois-asn-country.mmdb",
    "source_label": "ip-location-db geo-whois-asn-country (CC0-1.0), release 2.3.2026061719",
    "php_supported": true,
    "downloading": false
  }
}

Download the country database ​

POST /settings/geoip/download

Downloads the database, checks its checksum and format, and replaces the installed file.

Permission: manage_options

Body parameters: None.

Response: 200. data is the status object from the status route.

Errors:

StatusWhen
502The download failed. The code is geoip_download_failed and message says why, for example a download already running, a missing download address, a checksum that does not match, a file that is not a valid database, or a folder that cannot be written.

Remove the country database ​

POST /settings/geoip/remove

Deletes the downloaded file and its saved details.

Permission: manage_options

Body parameters: None.

Response: 200. data is the status object from the status route.

Errors:

StatusWhen
500The file could not be deleted. The code is geoip_remove_failed.

License ​

The license screen uses these routes. They hold the license key's status and the buttons to activate, deactivate, refresh and remove it. Each route returns the same license object. The screen is described in Activate your license.

The license object ​

Each license route returns this object, as data.license or, for GET /settings/license, as data itself.

FieldTypeWhat it holds
statusstringactive when the license counts as in use, else inactive.
statestringThe raw state: active, trial, grace, expired, suspended, revoked or inactive.
entitledbooleantrue for the active, trial and grace states.
recoverable_via_refreshbooleantrue when the state is not entitled, a key is saved and the state is not terminal.
recoverable_terminalbooleantrue for a revoked license.
has_cached_keybooleantrue when a key is saved on this site.
last_errorstringThe last validation error code, empty if none.
status_messagestringA sentence about the state.
license_key_maskedstringThe key with all but the last four characters replaced by *. Empty when no key is saved.
license_typestring or nulllifetime, renewal or one_time when entitled, else null.
valid_untilstring or nullThe end date, YYYY-MM-DD, when entitled and known, else null.
renewal_datestring or nullThe same date as valid_until.
activated_onstring or nullThe activation date, YYYY-MM-DD, when entitled and known, else null.
is_expiredbooleantrue when the state is expired.
is_gracebooleantrue when the state is grace.
is_trialbooleantrue when the state is trial.
planstringThe plan name, empty if unknown.
activations_usedintegerSites using the key.
activations_limitintegerSites the key allows.
manage_urlstringThe address of the account page.
renew_urlstringThe same address as manage_url.
messagestringThe last message from the license service.

When licensing is not available on the site, every route returns the inactive object: status and state are inactive, the booleans are false, the strings are empty, the integers are 0 and the nullable fields are null.

Get the license status ​

GET /settings/license

Returns the license object.

Permission: manage_options

Query parameters: None.

Response: 200. data is the license object.

json
{
  "success": true,
  "data": {
    "status": "active",
    "state": "active",
    "entitled": true,
    "recoverable_via_refresh": false,
    "recoverable_terminal": false,
    "has_cached_key": true,
    "last_error": "",
    "status_message": "Your license is active.",
    "license_key_masked": "************ABCD",
    "license_type": "renewal",
    "valid_until": "2027-10-20",
    "renewal_date": "2027-10-20",
    "activated_on": "2026-10-20",
    "is_expired": false,
    "is_grace": false,
    "is_trial": false,
    "plan": "Pro",
    "activations_used": 1,
    "activations_limit": 3,
    "manage_url": "https://example.com/account",
    "renew_url": "https://example.com/account",
    "message": ""
  }
}

Activate a license ​

POST /settings/license/activate

Activates a license key for this site.

Permission: manage_options

Body parameters:

NameTypeRequiredDefaultWhat it does
license_keystringYesnoneThe license key. Required, 8 to 255 characters.

Response: 200, with a top-level message from the license service. data holds:

FieldTypeWhat it holds
licenseobjectThe license object after activation.
json
{
  "success": true,
  "data": { "license": { "status": "active", "...": "..." } },
  "message": "License activated successfully."
}

Errors:

StatusWhen
422license_key is missing or outside 8 to 255 characters.
422The service refused the key. errors.license_key holds its message, for example Invalid license key. or Licensing is unavailable on this site.

Deactivate a license ​

POST /settings/license/deactivate

Deactivates the license for this site.

Permission: manage_options

Body parameters: None.

Response: 200, with a top-level message. data.license is the license object. A failed deactivation answers 424 with the code license_request_failed and the service's message.

Errors:

StatusWhen
424The code is license_unavailable. This site has no license client.
424The code is license_request_failed. The license service refused the call.

Refresh the license status ​

POST /settings/license/refresh

Asks the license service for the current state.

Permission: manage_options

Body parameters: None.

Response: 200, with a top-level message. data.license is the license object.

Errors:

StatusWhen
424The code is license_unavailable. Licensing is unavailable on this site.

Remove the license ​

POST /settings/license/remove

Removes the license from this site and deletes the local license data. If the license server cannot be reached, the message says so and tells you to release the site in your account.

Permission: manage_options

Body parameters: None.

Response: 200, with a top-level message. data.license is the license object.

Errors:

StatusWhen
424The code is license_unavailable. Licensing is unavailable on this site.

After you change a prefix, old short links keep working. These routes update the links inside your posts so they use the current prefix. The update works in three steps: check if there is anything to move, review what would change, then apply it in batches. A second pair of routes lists the posts one page at a time and applies the ones you pick.

What it changes. The apply route edits post content. It rewrites the href of <a> tags in posts, pages and other scanned content types that are published, scheduled or private. A link is rewritten only when it points to this site, has the exact shape /{previous-prefix}/{slug} and the slug belongs to an existing short link. With Affiliate link prefix set, a link that leads to an affiliate's domain moves straight to the affiliate prefix. Other links, and pages under the old prefix, stay as they are. In affiliate scope, the same rewrite moves affiliate links from your link prefix, internal prefix and previous prefixes to the affiliate prefix. With the affiliate prefix empty, it moves only links from previous affiliate prefixes. The plugin saves a revision of each post first, checks that the user can edit it, and keeps auto-masking from running during the save. Page builder data stored in post meta is listed, never changed. A second run finds nothing, because the rewritten links no longer match.

All five routes take the same two optional parameters in the query string (GET) or body (POST).

NameTypeRequiredDefaultWhat it does
fromstringNoall previous prefixesLimits the update to one previous prefix. Slashes at both ends are removed. A prefix in use now returns nothing to do.
scopestringNoprevious modeaffiliate moves only affiliate links to the affiliate prefix (Update affiliate links in posts). Any other value, or none, moves links from previous prefixes to the current ones. In affiliate scope, from is ignored.

GET /settings/prefix-links

Tells whether any post holds links under a previous prefix.

Permission: manage_options

Query parameters: from and scope, as above.

Response: 200. data holds:

FieldTypeWhat it holds
foundbooleantrue when at least one post has a link to move. After 500 candidate posts without a match it answers true anyway, and the review gives the real count.
prefixesarrayThe prefixes it checks. Each item has from (string) and to (string, empty when the target depends on the link).
json
{
  "success": true,
  "data": {
    "found": true,
    "prefixes": [{ "from": "link", "to": "go-to" }]
  }
}

GET /settings/prefix-links/review

Counts the posts and links that would change, with examples. Nothing is written. It reads up to 500 candidate posts per call.

Permission: manage_options

Query parameters:

NameTypeRequiredDefaultWhat it does
afterintegerNo0Continue after this post ID. Use the next value of the previous call.
fromstringNoallAs above.
scopestringNoprevious modeAs above.

Response: 200. data holds:

FieldTypeWhat it holds
postsintegerPosts that a save would change.
linksintegerLinks in those posts.
blockedintegerPosts a save would skip, for example when the user cannot edit them.
blocked_postsarrayUp to 20 skipped posts: the post fields below plus reason (string) and links (integer).
samplearrayUp to 10 posts: the post fields below plus links (integer) and example (object with before and after link text, or null).
found_inarray of stringsThe previous prefixes the links use.
moved_toobjectThe new prefix to the number of links that move to it. An empty list ([]) when none.
nextinteger or nullThe post ID to pass as after for the next call. null at the end.
prefixesarrayThe prefixes checked, as in the check route.
builderobjectOnly on the first call (after is 0) and when there are prefixes to check. posts lists up to 20 posts whose page builder data holds a previous prefix (post fields below). more is true if there are more.

The post fields in these lists are:

FieldTypeWhat it holds
idintegerThe post ID.
titlestringThe post title, or (no title).
typestringThe post type's singular label.
statusstringThe post status label.
edit_urlstringThe edit screen address.
view_urlstringThe permalink.
json
{
  "success": true,
  "data": {
    "posts": 3,
    "links": 5,
    "blocked": 0,
    "blocked_posts": [],
    "sample": [
      {
        "id": 12,
        "title": "Best widgets",
        "type": "Post",
        "status": "Published",
        "edit_url": "https://example.com/wp-admin/post.php?post=12&action=edit",
        "view_url": "https://example.com/best-widgets/",
        "links": 2,
        "example": {
          "before": "https://example.com/link/blue-widget",
          "after": "https://example.com/go-to/blue-widget"
        }
      }
    ],
    "found_in": ["link"],
    "moved_to": { "go-to": 5 },
    "next": null,
    "prefixes": [{ "from": "link", "to": "go-to" }],
    "builder": { "posts": [], "more": false }
  }
}

POST /settings/prefix-links/apply

Rewrites the links in the next 25 posts. Call it again with next as after until next is null.

Permission: manage_options. Each post also needs edit_post for the signed-in user. A post the user cannot edit is skipped, not failed.

Body parameters:

NameTypeRequiredDefaultWhat it does
afterintegerNo0Continue after this post ID. Use the next value of the previous call.
fromstringNoallAs above.
scopestringNoprevious modeAs above.

Response: 200. data holds:

FieldTypeWhat it holds
linksintegerLinks rewritten in this batch.
posts_changedintegerPosts saved in this batch.
skippedarrayPosts left unchanged: the post fields from the review plus reason (string) and links (integer). A post is skipped when the user cannot edit it, WordPress would remove markup on save, it changed during the run, the save would also change its status or address, or it could not be saved.
nextinteger or nullThe post ID to pass as after for the next call. null when the last batch is done.
bash
curl -X POST -u USER:APPLICATION_PASSWORD \
  -H "Content-Type: application/json" \
  -d '{"after":0}' \
  "https://example.com/wp-json/link-manager-pro/v1/settings/prefix-links/apply"
json
{
  "success": true,
  "data": {
    "links": 5,
    "posts_changed": 3,
    "skipped": [],
    "next": null
  }
}

Errors:

StatusWhen
409The update is already running in another window. The code is prefix_links_busy. The lock lasts at most 1 minute.

List posts to update ​

GET /settings/prefix-links/page

Returns the next 20 posts that have links to move, each with up to 3 of its changes. The update dialog lists them for you to tick. Nothing is written.

Permission: manage_options

Query parameters:

NameTypeRequiredDefaultWhat it does
afterintegerNo0Continue after this post ID. Use the next value of the previous call.
fromstringNoallAs above.
scopestringNoprevious modeAs above.

The route reads at most 500 candidate posts per call, so a page can hold fewer than 20 posts.

Response: 200. data holds:

FieldTypeWhat it holds
postsarrayThe posts: the post fields from the review plus links (integer), changes (up to 3 objects with before and after link text) and blocked (string, why a save would be refused, or null).
nextinteger or nullThe post ID to pass as after for the next call. null at the end.
json
{
  "success": true,
  "data": {
    "posts": [
      {
        "id": 12,
        "title": "Best widgets",
        "type": "Post",
        "status": "Published",
        "edit_url": "https://example.com/wp-admin/post.php?post=12&action=edit",
        "view_url": "https://example.com/best-widgets/",
        "links": 2,
        "changes": [
          { "before": "https://example.com/link/blue-widget", "after": "https://example.com/go-to/blue-widget" }
        ],
        "blocked": null
      }
    ],
    "next": null
  }
}

Apply the update to chosen posts ​

POST /settings/prefix-links/apply-posts

Rewrites the links in the posts you send, up to 20.

Permission: manage_options. Each post also needs edit_post for the signed-in user. A post the user cannot edit is skipped, not failed.

Body parameters:

NameTypeRequiredDefaultWhat it does
idsarray of integersYesnonePost IDs. Only the first 20 are used. A post outside the scanned content types or statuses, or with nothing to move, is left alone.
fromstringNoallAs above.
scopestringNoprevious modeAs above.

Response: 200. data holds links, posts_changed and skipped, as in Apply links update, without next.

bash
curl -X POST -u USER:APPLICATION_PASSWORD \
  -H "Content-Type: application/json" \
  -d '{"ids":[12,15]}' \
  "https://example.com/wp-json/link-manager-pro/v1/settings/prefix-links/apply-posts"
json
{
  "success": true,
  "data": {
    "links": 2,
    "posts_changed": 1,
    "skipped": []
  }
}

Errors:

StatusWhen
409The update is already running in another window. The code is prefix_links_busy.