Skip to content

Campaigns and UTM endpoints ​

These REST routes manage campaigns and report on the clicks of the short links tagged with a campaign value. They also store reusable UTM templates. Each entry lists the parameters, the response fields and the errors.

Sign-in, the response envelope and paging work as in the REST API overview.

All paths are relative to the base URL /wp-json/link-manager-pro/v1. Examples use https://example.com and an Application Password placeholder (USER:APPLICATION_PASSWORD). Inside WordPress, send the X-WP-Nonce header instead. Every route on this page needs link_manager_pro_access (Permissions).

What a campaign is ​

A campaign is one utm_campaign value. Two things can make a campaign exist:

  • Stored campaign. A row in the link_manager_pro_meta table with entity_type campaign, entity_id 0 and the campaign value as meta_key. The row keeps a name, a description and default UTM values. The API calls it managed: true.
  • Campaign value found on links. Any short link whose saved UTM parameters contain a utm_campaign value counts as part of that campaign, stored or not. A campaign that exists only this way has managed: false. The list screen calls it a virtual campaign, and type=virtual selects it.

The list route merges both. A partner's own utm_campaign value also creates a list row, with link_count 0 unless links use it. Deleting a stored campaign removes only its row. The links keep their UTM values, so the campaign shows again as managed: false.

The campaign value is called slug in every response and is the key used in the path. It is not a short link slug.

Campaign values in the path ​

The routes PUT /campaigns/{campaign}, DELETE /campaigns/{campaign} and every /campaigns/{name}/... route take the value from the path.

  • The path pattern matches [^/]+, so a value that contains / cannot be addressed by these routes.
  • The plugin runs rawurldecode on the value, removes tags and trims it. Send the value URL-encoded. A non-Latin value such as весна-2026 works when encoded.
  • Lookup of a stored campaign also tries the value as given, decoded once and the older percent-encoded lower-case spelling. A campaign stored by an earlier version under %d0%b2... still opens.
  • Links are matched by the exact value in their UTM parameters.

Timing and caching ​

Campaign figures are cached for 60 seconds, and creating, updating or deleting a campaign clears that cache. A report can lag behind a new click or a link edit by up to a minute.

Error shapes ​

An unknown campaign is not handled the same way on every route:

RouteUnknown campaign
PUT /campaigns/{campaign}422, with errors.slug "Campaign not found." (after the name check).
DELETE /campaigns/{campaign}404.
GET /campaigns/{name}/report and .../report/export404.
GET /campaigns/{name}/links200 with an empty page.
GET /campaigns/{name}/clicks200 with zero-filled days.
GET /campaigns/{name}/utm-breakdown200 with [].
GET /campaigns/{name}/countries200 with no rows.

A 404 from this page is a WordPress error { "code": "campaign_not_found", "message": "Campaign not found", "data": { "status": 404 } }. Validation failures are 422 as in the overview.

The campaign object ​

Two shapes carry a campaign. The stored campaign comes from GET /campaigns/all, create and update. The list row comes from GET /campaigns.

Stored campaign (all, create and update) ​

FieldTypeWhat it holds
idintegerThe ID of the row in link_manager_pro_meta.
slugstringThe campaign value, the utm_campaign text.
namestringThe display name.
descriptionstringThe description, empty when none.
utm_sourcestringDefault utm_source for new links in this campaign. Empty when none.
utm_mediumstringDefault utm_medium. Empty when none.
utm_termstringDefault utm_term. Empty when none.
utm_contentstringDefault utm_content. Empty when none.
link_countintegerLinks whose UTM parameters carry this value. Create and update only.
affiliate_countintegerAffiliates whose UTM campaign is this value. Create and update only.

List row ​

FieldTypeWhat it holds
slugstringThe campaign value.
namestringThe stored name, or the value itself for an unmanaged campaign.
descriptionstringThe stored description, empty otherwise.
managedbooleantrue for a stored campaign, false when only links carry the value.
link_countintegerShort links carrying the value.
affiliate_countintegerAffiliates whose UTM campaign is this value.
total_clicksintegerRecorded clicks on those links.
unique_clicksintegerRecorded clicks marked unique on those links.
sourcesarray of stringsThe distinct utm_source values of those links, and of a partner using the value.
mediumsarray of stringsThe distinct utm_medium values, collected the same way.
utm_source, utm_medium, utm_term, utm_contentstringThe stored campaign's defaults. Empty for an unmanaged campaign.

Campaigns ​

List campaigns ​

GET /campaigns

Returns one page of campaigns, stored and found on links together, with the click totals of each and counts for the filter chips.

Permission: link_manager_pro_access (Permissions)

Query parameters:

NameTypeRequiredDefaultWhat it does
searchstringNononeCase-insensitive match on part of the name or the value.
typestringNoallmanaged lists stored campaigns, virtual lists values found only on links. Any other value gives all.
sourcestringNononeKeeps campaigns that use this exact utm_source on a link, or as the stored default.
mediumstringNononeThe same for utm_medium.
pageintegerNo1The page number. A value below 1 gives page 1.
per_pageintegerNo15Rows per page. Kept between 1 and 100. A value below 1 gives 1, above 100 gives 100.

Rows are sorted by total_clicks, highest first, then by slug.

Response: 200. data is a smaller page object than the one in Pagination. It has no from, to or *_url fields.

FieldTypeWhat it holds
dataarray of objectsThe rows of this page, fields in the list row.
totalintegerCampaigns matching the filters, across all pages.
current_pageintegerThe page returned.
per_pageintegerRows per page after the 1 to 100 limit.
last_pageintegerThe last page number, at least 1.
facetsobjectsource and medium, each an array of { "value": string, "count": integer }, most used first. Counted after search and type, before source and medium.
countsobjectall, managed and virtual, each an integer. Counted after search, source and medium, before type.
bash
curl -u USER:APPLICATION_PASSWORD \
  "https://example.com/wp-json/link-manager-pro/v1/campaigns?type=managed&per_page=10"
json
{
  "success": true,
  "data": {
    "data": [
      {
        "slug": "spring-sale",
        "name": "Spring sale",
        "description": "",
        "managed": true,
        "link_count": 4,
        "affiliate_count": 0,
        "total_clicks": 312,
        "sources": ["newsletter", "twitter"],
        "mediums": ["email", "social"],
        "utm_source": "",
        "utm_medium": "",
        "utm_term": "",
        "utm_content": "",
        "unique_clicks": 240
      }
    ],
    "total": 1,
    "current_page": 1,
    "per_page": 10,
    "last_page": 1,
    "facets": {
      "source": [{ "value": "newsletter", "count": 1 }],
      "medium": [{ "value": "email", "count": 1 }]
    },
    "counts": { "all": 3, "managed": 1, "virtual": 2 }
  }
}

Errors: None specific to this route.

List every stored campaign ​

GET /campaigns/all

Returns all stored campaigns without paging, for a dropdown. Campaigns found only on links are not included.

Permission: link_manager_pro_access (Permissions)

Query parameters: None.

Response: 200. data is an array of stored campaigns, sorted by slug, without link_count. It is [] when none are stored.

json
{
  "success": true,
  "data": [
    {
      "id": 5,
      "slug": "spring-sale",
      "name": "Spring sale",
      "description": "",
      "utm_source": "",
      "utm_medium": "",
      "utm_term": "",
      "utm_content": ""
    }
  ]
}

Errors: None.

List campaign values not yet stored ​

GET /campaigns/sync/preview

Returns the campaign values found on links, and on partners, that have no stored campaign. Use it to offer "create a campaign from this value".

Permission: link_manager_pro_access (Permissions)

Query parameters: None.

Response: 200. data is an array, most clicks first, [] when every value is stored.

FieldTypeWhat it holds
slugstringThe campaign value.
suggested_namestringThe value with _ and - turned into spaces and each word capitalized.
link_countintegerShort links carrying the value.
total_clicksintegerRecorded clicks on those links.
sourcesarray of stringsThe distinct utm_source values.
mediumsarray of stringsThe distinct utm_medium values.
json
{
  "success": true,
  "data": [
    {
      "slug": "black-friday",
      "suggested_name": "Black Friday",
      "link_count": 2,
      "total_clicks": 57,
      "sources": ["newsletter"],
      "mediums": ["email"]
    }
  ]
}

Errors: None.

Create a campaign ​

POST /campaigns

Stores a campaign. To adopt a value found on links, send that value as slug so the campaign matches its links.

Permission: link_manager_pro_access (Permissions)

Body parameters: (JSON)

NameTypeRequiredDefaultWhat it does
namestringYesnoneThe display name. Tags are stripped and the text is trimmed.
slugstringNothe nameThe campaign value. A value already used on links is kept as written. Any other value goes through sanitize_title(), which gives lower-case letters, digits and hyphens, then URL-decoded.
descriptionstringNoemptyFree text. Line breaks are kept.
utm_sourcestringNoemptyDefault utm_source for the campaign.
utm_mediumstringNoemptyDefault utm_medium.
utm_termstringNoemptyDefault utm_term.
utm_contentstringNoemptyDefault utm_content.

A parameter that is not a string is treated as empty. The code sets no length limit on these fields.

Response: 200, not 201. data is the stored campaign with link_count and affiliate_count. message is "Campaign created successfully."

bash
curl -X POST -u USER:APPLICATION_PASSWORD \
  -H "Content-Type: application/json" \
  -d '{"name":"Spring sale","utm_medium":"email"}' \
  "https://example.com/wp-json/link-manager-pro/v1/campaigns"
json
{
  "success": true,
  "data": {
    "id": 5,
    "slug": "spring-sale",
    "name": "Spring sale",
    "description": "",
    "utm_source": "",
    "utm_medium": "email",
    "utm_term": "",
    "utm_content": "",
    "link_count": 0
  },
  "message": "Campaign created successfully."
}

Errors:

StatusWhen
422name is empty: errors.name "Campaign name is required."
422The campaign value is empty, for example a name made only of symbols: errors.slug "Campaign value is required."
422A stored campaign already has this value: errors.slug "A campaign with this value already exists."

Update a campaign ​

PUT /campaigns/{campaign}

Replaces a stored campaign. The call writes every field, so a description or utm_* field you leave out is saved empty.

Permission: link_manager_pro_access (Permissions)

Path parameters:

NameTypeWhat it is
campaignstringThe current campaign value, URL-encoded. See Campaign values in the path.

Body parameters: (JSON)

NameTypeRequiredDefaultWhat it does
namestringYesnoneThe display name. It is required on every update.
slugstringNothe current valueThe new campaign value. Left out, empty, or equal to the current value (in any spelling) means no rename.
descriptionstringNoemptyReplaces the description. Omitted means saved as empty.
utm_sourcestringNoemptyReplaces the default. Omitted means saved as empty.
utm_mediumstringNoemptyThe same.
utm_termstringNoemptyThe same.
utm_contentstringNoemptyThe same.

A new slug is normalized like on create. It cannot change while any link uses the current value in its UTM parameters.

Response: 200. data is the stored campaign with link_count and affiliate_count for the value now stored. message is "Campaign updated successfully."

bash
curl -X PUT -u USER:APPLICATION_PASSWORD \
  -H "Content-Type: application/json" \
  -d '{"name":"Spring sale 2026","description":"Email push","utm_medium":"email"}' \
  "https://example.com/wp-json/link-manager-pro/v1/campaigns/spring-sale"
json
{
  "success": true,
  "data": {
    "id": 5,
    "slug": "spring-sale",
    "name": "Spring sale 2026",
    "description": "Email push",
    "utm_source": "",
    "utm_medium": "email",
    "utm_term": "",
    "utm_content": "",
    "link_count": 4
  },
  "message": "Campaign updated successfully."
}

Errors:

StatusWhen
422name is empty: errors.name "Campaign name is required." This check runs first, so an unknown campaign with an empty name gets this error.
422The new value is empty: errors.slug "Campaign value is required."
422No stored campaign has the value in the path: errors.slug "Campaign not found." A value that exists only on links is not stored, so it also gets this.
422A rename while links use the current value: errors.slug "Can’t be changed: N link uses it in its UTM tags." (plural for more than one link).
422A rename while affiliates use the current value and no link does: errors.slug "Can’t be changed: N affiliate uses it in its UTM tags." (plural: "N affiliates use it in their UTM tags."). The link check runs first.
422A rename to a value another stored campaign has: errors.slug "A campaign with this value already exists."

Delete a campaign ​

DELETE /campaigns/{campaign}

Deletes the stored campaign. Links keep their UTM values, so the value stays in reports as an unmanaged campaign.

Permission: link_manager_pro_access (Permissions)

Path parameters:

NameTypeWhat it is
campaignstringThe campaign value, URL-encoded.

Response: 200. data is null. message is "Campaign deleted."

json
{
  "success": true,
  "data": null,
  "message": "Campaign deleted."
}

Errors:

StatusWhen
404No stored campaign has that value, including one that exists only on links. The code is campaign_not_found.

Campaign reports ​

A campaign report covers every recorded click on the campaign's short links. None of the report routes except clicks and the CSV export take date parameters, so their numbers are all-time. The /campaigns/{name}/... routes need the campaign value in the path, so the rules for values in the path apply.

Get a campaign report ​

GET /campaigns/{name}/report

Returns the totals, first and last click and top breakdowns for one campaign, all-time.

Permission: link_manager_pro_access (Permissions)

Path parameters:

NameTypeWhat it is
namestringThe campaign value, URL-encoded.

Query parameters: None. There is no date range; the report is all-time.

Response: 200. data holds:

FieldTypeWhat it holds
slugstringThe campaign value.
campaignstringThe same value as slug.
namestringThe stored name, or the value for an unmanaged campaign.
descriptionstringThe stored description, empty otherwise.
managedbooleanWhether a stored campaign exists.
created_atstring or nullWhen the stored campaign was created, UTC Y-m-d H:i:s. null for an unmanaged campaign.
first_click_atstring or nullThe earliest recorded click, UTC Y-m-d H:i:s. null without clicks.
last_click_atstring or nullThe latest recorded click, same format.
link_countintegerShort links carrying the value.
affiliate_countintegerAffiliates whose UTM campaign is this value.
total_clicksintegerRecorded clicks on those links.
unique_clicksintegerClicks marked unique.
top_sourcestring or nullThe tagged utm_source with the most clicks, ties broken alphabetically. If no link with clicks has a source, the first source found on the links, else null.
sourcesarray of stringsThe distinct utm_source values of the links, in link ID order.
breakdownsobjectsource, medium, device, browser and os. Each is { "items": [{ "label": string, "count": integer }], "total": integer }. items holds at most 10 labels, largest first. total counts all clicks in the breakdown, not only the listed ones. Links without a source or medium count under the label "(not set)".
utm_source, utm_medium, utm_term, utm_contentstringThe stored campaign's defaults. Empty for an unmanaged campaign.
trackingobjectThe site's tracking state, fields below.

tracking holds:

FieldTypeWhat it holds
tracking_enabledbooleanWhether click tracking is on.
retention_daysintegerDays clicks are kept, 0 for no limit.
autolink_site_enabledbooleanWhether auto-linking is on site-wide.
history_trustedbooleanWhether the click history is complete enough to compare periods.
history_reasonstringWhy not, such as tracking_off, retention_short, tracking_resumed, retention_changed, clicks_restored or gap. Empty when trusted.
bash
curl -u USER:APPLICATION_PASSWORD \
  "https://example.com/wp-json/link-manager-pro/v1/campaigns/spring-sale/report"
json
{
  "success": true,
  "data": {
    "slug": "spring-sale",
    "name": "Spring sale",
    "description": "",
    "managed": true,
    "campaign": "spring-sale",
    "created_at": "2026-10-01 09:00:00",
    "first_click_at": "2026-10-02 07:41:12",
    "last_click_at": "2026-10-22 18:03:09",
    "link_count": 4,
    "affiliate_count": 0,
    "total_clicks": 312,
    "unique_clicks": 240,
    "top_source": "newsletter",
    "sources": ["newsletter", "twitter"],
    "breakdowns": {
      "source": { "items": [{ "label": "newsletter", "count": 200 }], "total": 312 },
      "medium": { "items": [{ "label": "email", "count": 200 }], "total": 312 },
      "device": { "items": [{ "label": "Desktop", "count": 180 }], "total": 312 },
      "browser": { "items": [{ "label": "Chrome", "count": 150 }], "total": 312 },
      "os": { "items": [{ "label": "Windows", "count": 120 }], "total": 312 }
    },
    "utm_source": "",
    "utm_medium": "",
    "utm_term": "",
    "utm_content": "",
    "tracking": {
      "tracking_enabled": true,
      "retention_days": 0,
      "autolink_site_enabled": true,
      "history_trusted": true,
      "history_reason": ""
    }
  }
}

Errors:

StatusWhen
404The value is neither stored, nor on any link, nor used by a partner. The code is campaign_not_found. A value that only a partner uses answers 200 with link_count 0 and managed false.

Export a campaign report as CSV ​

GET /campaigns/{name}/report/export

Downloads every recorded click on the campaign's short links as a CSV file, newest first.

Permission: link_manager_pro_access (Permissions)

Path parameters:

NameTypeWhat it is
namestringThe campaign value, URL-encoded.

Query parameters:

NameTypeRequiredDefaultWhat it does
startstringNononeFirst day, Y-m-d in site time. A malformed or impossible date counts as missing.
endstringNononeLast day, Y-m-d. The whole day counts.

Response: 200. The body is CSV, not JSON, with no envelope. The headers are:

HeaderValue
Content-Typetext/csv; charset=utf-8
Content-Dispositionattachment; filename="link-manager-pro-report-{value}-{Y-m-d}.csv". The value goes through sanitize_file_name(), and the date is today in site time.
Cache-Controlno-cache, no-store, must-revalidate

The first row is a header row. Each later row is one click. The columns, in this order:

ColumnWhat it holds
Clicked AtThe click time in site time, Y-m-d H:i:s.
Link SlugThe slug of the short link that was clicked.
Target URLThe destination recorded for the click.
CountryA two-letter country code, or empty.
Referrer DomainThe referring host, or empty.
Referrer URLThe referring page, or empty.
DeviceThe device type from the user agent.
OSThe operating system family.
BrowserThe browser family.
Uniqueyes or no.
Post IDThe post the click came from, or empty.
UTM Source, UTM Medium, UTM Campaign, UTM Term, UTM ContentThe UTM values recorded for the click, or empty.

A cell that starts with =, +, -, @, a tab or a carriage return gets a leading single quote, so a spreadsheet does not run it as a formula. A stored campaign with no links gives only the header row.

bash
curl -u USER:APPLICATION_PASSWORD -o campaign.csv \
  "https://example.com/wp-json/link-manager-pro/v1/campaigns/spring-sale/report/export?start=2026-10-01&end=2026-10-23"
text
Clicked At,"Link Slug","Target URL",Country,"Referrer Domain","Referrer URL",Device,OS,Browser,Unique,"Post ID","UTM Source","UTM Medium","UTM Campaign","UTM Term","UTM Content"
"2026-10-22 18:03:09",blue-widget,"https://shop.example.com/?tag=mysite-20",US,,,Desktop,Windows,Chrome,yes,18,newsletter,email,spring-sale,,

Errors:

StatusWhen
404The value is neither stored, nor on any link, nor used by a partner. The body is the JSON campaign_not_found error. A value only a partner uses answers 200 with an empty report.

GET /campaigns/{name}/links

Returns one page of the short links that carry the campaign value, the most clicked first.

Permission: link_manager_pro_access (Permissions)

Path parameters:

NameTypeWhat it is
namestringThe campaign value, URL-encoded.

Query parameters:

NameTypeRequiredDefaultWhat it does
pageintegerNo1The page number. A value below 1 gives page 1.
per_pageintegerNo10Links per page. Kept between 1 and 100.

Links are sorted by recorded clicks, highest first, then by ID.

Response: 200. With links, data is a page object (Pagination). Each item in data.data has the fields of the short link object (without the list-only fields such as rules_count) and these extra fields:

FieldTypeWhat it holds
clicksintegerRecorded clicks on this link. click_count is the separate lifetime counter.
urlstringThe destination, the same text as target_url.
utm_sourcestringThe link's utm_source, empty when none.
utm_mediumstringThe link's utm_medium, empty when none.

The short_url field uses the affiliate prefix for links to an affiliate's domain, as described under Short links.

With no links, including an unknown campaign, data is only { "data": [], "total": 0, "current_page": 1, "per_page": 10, "last_page": 1 }. current_page and per_page repeat the request.

json
{
  "success": true,
  "data": {
    "current_page": 1,
    "data": [
      {
        "id": 42,
        "slug": "blue-widget",
        "target_url": "https://example.com/products/blue-widget",
        "redirect_type": 301,
        "utm_params": { "utm_source": "newsletter", "utm_medium": "email", "utm_campaign": "spring-sale" },
        "click_count": 128,
        "short_url": "https://example.com/link/blue-widget",
        "clicks": 120,
        "url": "https://example.com/products/blue-widget",
        "utm_source": "newsletter",
        "utm_medium": "email"
      }
    ],
    "last_page": 1,
    "per_page": 10,
    "total": 1
  }
}

The example shortens the link fields. A real row has all of them.

Errors: None. An unknown campaign returns the empty page.

Get campaign clicks over time ​

GET /campaigns/{name}/clicks

Returns one row per day with the total and unique clicks on the campaign's links, for a chart, or the same split by utm_source.

Permission: link_manager_pro_access (Permissions)

Path parameters:

NameTypeWhat it is
namestringThe campaign value, URL-encoded.

Query parameters:

NameTypeRequiredDefaultWhat it does
startstringNosee belowFirst day, Y-m-d in site time.
endstringNotodayLast day, Y-m-d. Never later than today.
groupstringNononesource splits the series by each link's own utm_source. Any other value is ignored.

With neither start nor end, the range runs from the earlier of the stored campaign's creation day and its first click, up to today. With neither of those, it is the last 30 days. With only end, it covers the 30 days ending on that day. A reversed range is swapped, and a range is capped at 7305 days. An invalid date counts as missing.

Response: 200. Without group, data is an array with one item per day, zero-filled:

FieldTypeWhat it holds
datestringThe day, Y-m-d in site time.
totalintegerClicks that day.
uniqueintegerUnique clicks that day.

With group=source, data is { "series": [ ... ] }. Each series item has:

FieldTypeWhat it holds
keystringA utm_source value, "" for links with no source, or "__other" for all remaining sources.
pointsarray of objectsDays with date, total and unique, as above.

The three sources with the most recorded clicks get their own item, and the rest merge into __other. An unknown campaign gives series: [].

bash
curl -u USER:APPLICATION_PASSWORD \
  "https://example.com/wp-json/link-manager-pro/v1/campaigns/spring-sale/clicks?start=2026-10-20&end=2026-10-22"
json
{
  "success": true,
  "data": [
    { "date": "2026-10-20", "total": 14, "unique": 11 },
    { "date": "2026-10-21", "total": 9, "unique": 7 },
    { "date": "2026-10-22", "total": 0, "unique": 0 }
  ]
}

Errors: None. An unknown campaign returns zero-filled days for the last 30 days, or an empty series with group=source.

Get a campaign's UTM breakdown ​

GET /campaigns/{name}/utm-breakdown

Returns the campaign's clicks grouped by the combination of utm_source, utm_medium, utm_term and utm_content on its links, all-time.

Permission: link_manager_pro_access (Permissions)

Path parameters:

NameTypeWhat it is
namestringThe campaign value, URL-encoded.

Query parameters: None.

Response: 200. data is an array, most clicks first. It is [] when no link carries the value. Each item is one distinct combination:

FieldTypeWhat it holds
utm_sourcestringThe utm_source, empty when none.
utm_mediumstringThe utm_medium, empty when none.
utm_termstringThe utm_term, empty when none.
utm_contentstringThe utm_content, empty when none.
link_countintegerLinks with this combination.
clicksintegerRecorded clicks on those links.
unique_clicksintegerRecorded clicks marked unique on those links.
json
{
  "success": true,
  "data": [
    {
      "utm_source": "newsletter",
      "utm_medium": "email",
      "utm_term": "",
      "utm_content": "header-button",
      "link_count": 2,
      "clicks": 200,
      "unique_clicks": 160
    }
  ]
}

Errors: None. An unknown campaign returns [].

Get a campaign's clicks by country ​

GET /campaigns/{name}/countries

Returns the campaign's recorded clicks grouped by country, all-time, for a map.

Permission: link_manager_pro_access (Permissions)

Path parameters:

NameTypeWhat it is
namestringThe campaign value, URL-encoded.

Query parameters: None.

Response: 200. data holds:

FieldTypeWhat it holds
rowsarray of objectsOne item per country, most clicks first: code (string, two capital letters) and total (integer).
totalintegerAll recorded clicks on the campaign's links, including those with no known country.
unresolvedintegerClicks with an empty, XX or invalid country code. They are not in rows.
json
{
  "success": true,
  "data": {
    "rows": [
      { "code": "US", "total": 180 },
      { "code": "DE", "total": 60 }
    ],
    "total": 312,
    "unresolved": 72
  }
}

Errors: None. An unknown campaign returns { "rows": [], "total": 0, "unresolved": 0 }.

UTM templates ​

A UTM template is a saved set of five UTM values with a name. The link form uses it to fill the UTM fields. Templates are rows in link_manager_pro_meta with entity_type utm_template and entity_id 0. The name is the meta_key, so names are unique.

The UTM template object ​

FieldTypeWhat it holds
idintegerThe row ID. It is the {id} in the update and delete paths.
namestringThe template name.
utm_sourcestringEmpty when not set.
utm_mediumstringEmpty when not set.
utm_campaignstringEmpty when not set.
utm_termstringEmpty when not set.
utm_contentstringEmpty when not set.

List UTM templates ​

GET /utm-templates

Returns every saved UTM template, oldest first.

Permission: link_manager_pro_access (Permissions)

Query parameters: None. There is no paging.

Response: 200. data is an array of UTM templates, [] when none.

json
{
  "success": true,
  "data": [
    {
      "id": 3,
      "name": "Newsletter",
      "utm_source": "newsletter",
      "utm_medium": "email",
      "utm_campaign": "",
      "utm_term": "",
      "utm_content": ""
    }
  ]
}

Errors: None.

Create a UTM template ​

POST /utm-templates

Saves a new UTM template.

Permission: link_manager_pro_access (Permissions)

Body parameters: (JSON)

NameTypeRequiredDefaultWhat it does
namestringYesnoneThe template name, trimmed and cleaned as plain text. It must be unique.
utm_sourcestringNoemptyThe utm_source value.
utm_mediumstringNoemptyThe utm_medium value.
utm_campaignstringNoemptyThe utm_campaign value.
utm_termstringNoemptyThe utm_term value.
utm_contentstringNoemptyThe utm_content value.

Response: 200, not 201. data.template is the UTM template. message is "UTM template saved."

bash
curl -X POST -u USER:APPLICATION_PASSWORD \
  -H "Content-Type: application/json" \
  -d '{"name":"Newsletter","utm_source":"newsletter","utm_medium":"email"}' \
  "https://example.com/wp-json/link-manager-pro/v1/utm-templates"
json
{
  "success": true,
  "data": {
    "template": {
      "id": 3,
      "name": "Newsletter",
      "utm_source": "newsletter",
      "utm_medium": "email",
      "utm_campaign": "",
      "utm_term": "",
      "utm_content": ""
    }
  },
  "message": "UTM template saved."
}

Errors:

StatusWhen
422name is empty: errors.name "Template name is required."
422Another template has this name: errors.name "A template with this name already exists."

Update a UTM template ​

PUT /utm-templates/{id}

Replaces a template. Every field is written, so a utm_* field you leave out is saved empty.

Permission: link_manager_pro_access (Permissions)

Path parameters:

NameTypeWhat it is
idintegerThe template ID.

Body parameters: (JSON) The same as Create a UTM template. name is required again.

Response: 200. data.template is the updated UTM template. message is "UTM template updated."

bash
curl -X PUT -u USER:APPLICATION_PASSWORD \
  -H "Content-Type: application/json" \
  -d '{"name":"Newsletter","utm_source":"newsletter","utm_medium":"email","utm_campaign":"spring-sale"}' \
  "https://example.com/wp-json/link-manager-pro/v1/utm-templates/3"
json
{
  "success": true,
  "data": {
    "template": {
      "id": 3,
      "name": "Newsletter",
      "utm_source": "newsletter",
      "utm_medium": "email",
      "utm_campaign": "spring-sale",
      "utm_term": "",
      "utm_content": ""
    }
  },
  "message": "UTM template updated."
}

Errors:

StatusWhen
404No template has that ID. The code is not_found. This check runs before validation.
422name is empty: errors.name "Template name is required."
422Another template has this name: errors.name "A template with this name already exists."

Delete a UTM template ​

DELETE /utm-templates/{id}

Deletes a saved template. Links that used its values keep them.

Permission: link_manager_pro_access (Permissions)

Path parameters:

NameTypeWhat it is
idintegerThe template ID.

Response: 200. data is null. message is "UTM template deleted."

json
{
  "success": true,
  "data": null,
  "message": "UTM template deleted."
}

Errors:

StatusWhen
404No template has that ID. The code is not_found.