# REST API overview

Link Manager Pro adds a REST API to WordPress under one base URL. This page covers how to sign in, which permission each route needs, and how requests, responses and paging work.
Page: https://docs.linkmanagerpro.com/developers/rest-api/
Use when someone asks: link manager pro rest api; link-manager-pro/v1; link manager pro api authentication; link manager pro api nonce
Intent: Reference

The Link Manager Pro REST API lives at `/wp-json/link-manager-pro/v1`. Sign in with the WordPress login cookie plus a nonce, or with a WordPress Application Password, and send JSON.

Each route has a full entry on its resource page; [Endpoint reference](#which-routes-to-rely-on) lists the pages.

## Base URL

Every route sits under one namespace:

```text
https://example.com/wp-json/link-manager-pro/v1
```

The plugin registers its routes on the WordPress `rest_api_init` action with `register_rest_route()`, so they behave like any other WordPress REST route. Source: `app/Providers/RouteServiceProvider.php` and `packages/framework/src/Http/WPRouterAdapter.php`.

## Authentication

Almost every route needs a signed-in WordPress user. How you sign in depends on where the request comes from.

### Inside WordPress: cookie and nonce

Code that runs in the browser of a logged-in user sends the login cookie automatically. Add the `X-WP-Nonce` header with a nonce created for the action `wp_rest`. Without the header, WordPress treats the request as logged out and the route answers `401`. With an invalid or expired nonce, WordPress refuses it with `403` and the code `rest_cookie_invalid_nonce`.

Create the nonce in PHP and pass it to your script:

```php
add_action( 'admin_enqueue_scripts', 'myprefix_enqueue_lmp_script' );
function myprefix_enqueue_lmp_script(): void {
	wp_enqueue_script( 'myprefix-lmp', plugins_url( 'lmp.js', __FILE__ ), array(), '1.0', true );
	wp_add_inline_script(
		'myprefix-lmp',
		'window.myprefixLmp = ' . wp_json_encode(
			array(
				'root'  => esc_url_raw( rest_url( 'link-manager-pro/v1' ) ),
				'nonce' => wp_create_nonce( 'wp_rest' ),
			)
		) . ';',
		'before'
	);
}
```

Then call a route with `fetch`:

```js
const response = await fetch(`${window.myprefixLmp.root}/links?per_page=10`, {
  credentials: 'same-origin',
  headers: { 'X-WP-Nonce': window.myprefixLmp.nonce },
});
const body = await response.json();
console.log(body.data.data);
```

The plugin's own screens use the same two pieces. They read `linkManagerProData.root` and `linkManagerProData.nonce`.

### Outside WordPress: Application Passwords

For scripts and servers, use WordPress Application Passwords with HTTP Basic authentication. This is WordPress's own mechanism for REST requests. WordPress offers it on sites served over HTTPS and on sites set to the `local` environment type. Create a password on the user's profile screen in WordPress, then send it as the Basic password:

```bash
curl -u USER:APPLICATION_PASSWORD \
  "https://example.com/wp-json/link-manager-pro/v1/links?per_page=10"
```

The routes check the permissions of the user who signs in. Link Manager Pro does not add its own API keys or tokens.

## Permissions

A route is public, or it checks the signed-in user in a fixed order. Each route group has one of four levels:

| Level | Who passes | Used by |
|---|---|---|
| Public | Anyone | `POST /track/internal-click`, which checks its origin and, for logged-in visitors only, a nonce |
| Logged in, plus `link_manager_pro_access` | Users with the plugin's access capability | Most routes: links, categories, tags, stats, reports, health, audits, and more |
| Logged in, plus `manage_options` | Administrators | `/system-info`, `/webhooks`, most `/settings` routes, and a few setup and redirect-review routes |
| Logged in, plus `edit_posts` or `link_manager_pro_access` | Anyone who can edit posts, or has the access capability | `POST /links`, `GET /editor/optimization/{id}`, `/ai/*` |

`link_manager_pro_access` is not stored on a role. The plugin works it out on every check, through the `user_has_cap` filter. A user has it when they are an administrator, meaning they have `manage_options`. They also have it when they hold the capability chosen in the access setting. The setting is **Who can use Link Manager Pro** under Link Manager › Settings › Advanced. A missing or unknown value falls back to administrators only. [Access for editors](https://docs.linkmanagerpro.com/guides/access) explains the choices.

The `edit_posts` level checks the capability returned by the `link_manager_pro_picker_capability` filter, which is `edit_posts` by default. It is the level the editor tools use. `POST /links` also needs the right to create: full plugin access, or the capability returned by the `link_manager_pro_picker_create_capability` filter, which is `publish_posts` by default. Without it the route returns `403` with the code `lmp_cannot_create_links`. A contributor can insert existing short links but can't create one.

A request that fails a check gets one of two answers:

| Status | Code | Meaning |
|---|---|---|
| `401` | `rest_not_logged_in` | The request is not signed in. A cookie request with no nonce also lands here. |
| `403` | `rest_forbidden` | The user is signed in but lacks the capability. |

## Requests

Send the body as JSON with `Content-Type: application/json`. The plugin reads form-encoded bodies too, and falls back to the JSON body when there are no form fields.

Routes use `GET`, `POST`, `PUT`, `PATCH` and `DELETE`. When a client or proxy only allows `POST`, send the real method in the `X-HTTP-Method-Override` header, or in the `_method` query parameter. Both come from WordPress core, which applies them before the route runs. The plugin's own screens use the header.

```bash
curl -X POST \
  -u USER:APPLICATION_PASSWORD \
  -H "X-HTTP-Method-Override: DELETE" \
  "https://example.com/wp-json/link-manager-pro/v1/links/123"
```

## Responses

Most routes answer with one envelope:

```json
{
  "success": true,
  "data": {},
  "message": "Optional human-readable text",
  "meta": {}
}
```

`success` and `data` are always present. `message` appears when the route has one to show, and `meta` when it has extra values. A route that creates something can answer `201` instead of `200`.

Errors take one of these shapes:

| Status | Shape | When |
|---|---|---|
| `422` | `{ "message": "...", "errors": { "field": ["..."] } }` | The request failed validation. `message` is the first field message, and `errors` is an empty object when no field applies. |
| `404` | `{ "code": "not_found", "message": "...", "data": { "status": 404 }, "status": 404 }` | The record does not exist. |
| `401`, `403` | `{ "code": "...", "message": "...", "data": { "status": 403 } }` | A permission check failed. Errors from a controller also carry `status` at the top level. |
| `500` | WordPress error with the code `route_error` | An unexpected error. The details go to the PHP error log, not to the response. |

Errors a controller returns, such as `400`, `401`, `403` and `404`, have the same shape: `code`, `message`, `data.status` and a top-level `status`. Check the HTTP status first, and then read `message`.

## Pagination

List routes that page their results take two query parameters:

| Name | Type | Default | What it does |
|---|---|---|---|
| `page` | int | `1` | The page to return. A value below 1 or not a whole number gives page 1. |
| `per_page` | int | `15` | Rows per page. On the standard page object, a value below 1 or not a whole number gives the default, and there is no upper limit. Some lists build their own page object and keep `per_page` between 1 and 100, with their own default, such as the campaign list and an affiliate's posts and links. Each entry says which. |

The page comes back inside the envelope's `data`:

```json
{
  "success": true,
  "data": {
    "current_page": 1,
    "data": [],
    "first_page_url": "/wp-json/link-manager-pro/v1/links?page=1",
    "from": null,
    "last_page": 1,
    "last_page_url": "/wp-json/link-manager-pro/v1/links?page=1",
    "links": [],
    "next_page_url": null,
    "path": "/wp-json/link-manager-pro/v1/links",
    "per_page": 15,
    "prev_page_url": null,
    "to": null,
    "total": 0
  }
}
```

`data.data` holds the rows. `from` and `to` are the row numbers on the page, and are `null` on an empty page. `total` is the row count across all pages. `links` lists the page links. `path` and the `*_url` fields are paths built from the request, without the host.

## Endpoint reference
Every route has a full entry with its parameters, response fields, an example and its errors, grouped by resource:

| Resource | Page |
|---|---|
| Short links, routing rules, other addresses, categories and tags | [Short links](https://docs.linkmanagerpro.com/developers/rest-api/links) |
| Affiliates and their reports | [Affiliates](https://docs.linkmanagerpro.com/developers/rest-api/affiliates) |
| Redirects and the review of old addresses | [Redirects](https://docs.linkmanagerpro.com/developers/rest-api/redirects) |
| Site stats and a short link's report | [Reports and stats](https://docs.linkmanagerpro.com/developers/rest-api/reports) |
| Campaigns and UTM templates | [Campaigns and UTM](https://docs.linkmanagerpro.com/developers/rest-api/campaigns) |
| Link Health, Link Audit, Link Equity and the content scan | [Link Health and Audit](https://docs.linkmanagerpro.com/developers/rest-api/link-health) |
| Webhook endpoints and deliveries | [Webhooks](https://docs.linkmanagerpro.com/developers/rest-api/webhooks) |
| Settings, the country database, SEO import and prefix updates | [Settings](https://docs.linkmanagerpro.com/developers/rest-api/settings) |
| Backups, imports from other plugins and shortcode replacement | [Backup and import](https://docs.linkmanagerpro.com/developers/rest-api/backup-migration) |
| The AI Link Assistant, setup, system info and internal click tracking | [Editor, AI and setup](https://docs.linkmanagerpro.com/developers/rest-api/editor-ai) |

[All endpoints](https://docs.linkmanagerpro.com/developers/rest-api/endpoints) lists every route on one page, each linked to its entry.

The routes serve the plugin's own screens, so a release can change a route or a field. Read the [Changelog](https://docs.linkmanagerpro.com/changelog) before you update an integration.

## Related

- [Short links endpoints](https://docs.linkmanagerpro.com/developers/rest-api/links)
- [All endpoints](https://docs.linkmanagerpro.com/developers/rest-api/endpoints)
- [Access for editors](https://docs.linkmanagerpro.com/guides/access)
- [Hooks overview](https://docs.linkmanagerpro.com/developers/hooks/)
