Skip to content
Coritan Docs

Make campaign links and read their results

Save a landing link with its UTM words and an optional coupon, copy the full address, and read the visits, sign-ups and orders each campaign brought in.

View as Markdown

A campaign link is an address on your storefront with UTM words added, so the tracker can tell which campaign a visit came from. You save each link once, copy its full address into an email, a post or an ad, and read what it brought in: visits, sign-ups, orders and revenue.

Your team manages campaign links in the staff console, under Marketing, as Run campaign links and referrals in the staff console describes. The owner and scripts use the API, which this page describes.

  • You need the Billing, Admin or Owner role in the organization. Support and read-only members cannot read or change campaigns.
  • Your storefront must be one that Coritan serves, so the tracker records each visit. How customers and orders are attributed explains what it records.
  • A campaign that applies a coupon needs that coupon to exist already. Make it first, as Staff billing and orders describes.

Owners, admins and billing members save campaign links in the dashboard. Your team can do the same in the staff console, as Marketing campaigns and referrals in the staff console describes. A member with another role does not see the page, and its address opens Overview.

  1. In the dashboard, open the organization, then Growth, then Campaigns.
  2. Under New campaign, fill in "Name", "Landing page", "Source", "Medium" and "Campaign word". The landing page is a path on your storefront, such as /pricing. "Term", "Content", "Coupon" and "Notes" are optional, and a coupon must already exist in your store.
  3. Read the Link box under the fields. It follows what you type and shows the whole address with every word encoded. A field that is wrong shows its reason once you have typed in it. Select the copy button in the box to copy the link. When we know no address for your storefront, the link is the path alone, and the page says to put your storefront's address in front of it.
  4. Select Create campaign. A name that another campaign has, in any capitals, and a coupon your store does not have are refused with the API's sentence under the field. The page keeps what you typed so you can fix it.
  5. Under Campaigns, choose the range: "7 days", "30 days", "90 days" or "12 months". Each campaign shows its Visits, Sign-ups, Orders, Revenue and Conversion for that range. When orders came in more than one currency, a switch for each currency appears beside the range, because money is never added across currencies. On a narrow screen some columns are left out.

When it works, the page says "Summer launch created." and the campaign appears under Campaigns with its figures. Active, Archived and All choose which campaigns the list shows, and the search box finds one by its name or its campaign word.

To change a campaign, open the actions menu of its row and select Edit. The builder shows the saved fields, and Save changes sends only what you changed. Archive… takes it out of the Active list and keeps its link and its results, Restore brings it back, and Delete… removes the saved link only. The visits and orders that carried its words stay in your reports. The copy button beside the menu copies a campaign's link without opening it.

Campaigns in your reports that are not saved lists the campaign words that visits and orders carried and no saved campaign names. Select Save as campaign on a row to fill the builder with that word, then finish the other fields.

Field What it is
name What you call the campaign, at most 120 characters. Each campaign in an organization has its own name, and two names that differ only in capitals count as one.
base_path The page the link opens, such as /pricing. It starts with / and is at most 400 characters. It holds no scheme, host, fragment (#), backslash, space or control character, and at most one ?. Left out, it is /.
utm_source Where the visit comes from, such as newsletter. At most 80 characters.
utm_medium How the link is delivered, such as email. At most 80 characters.
utm_campaign The word the reports group by. At most 120 characters.
utm_term The keyword or audience. At most 120 characters. Optional.
utm_content Tells two links of one campaign apart. At most 120 characters. Optional.
coupon_code A coupon of your organization that the link applies. Optional.
notes A note for your team, at most 500 characters. Optional.

name, utm_source, utm_medium and utm_campaign are required. A UTM word is trimmed, and it cannot hold &, =, # or ?, or a control character. An organization keeps at most 500 campaigns.

Every campaign has a url, the full link to copy. It is your storefront's address, then base_path, then the UTM words in the order utm_source, utm_medium, utm_campaign, utm_term and utm_content, then coupon when the campaign has a coupon code. We percent encode every value, so a word with a space or an & in it cannot add a parameter of its own.

A base_path may already hold a query, such as /pricing?plan=pro. We keep it and add the UTM words after it. We take any utm_ word, and coupon when the campaign has a coupon code, out of that query first, so nothing appears twice and the campaign's own words win. The saved base_path is never rewritten.

The address in front is your custom domain. With none, it is the public address Coritan uses for links in your emails. When we have neither, the link is the path and query alone, such as /pricing?utm_source=newsletter, and you add your storefront's address in front of it.

A storefront that Coritan serves reads coupon from any page. It keeps the code for the visit and for seven days in that browser, and fills the coupon field at checkout. A code the buyer typed is never replaced, and the code goes when the buyer removes it or an order uses it.

  1. Check the link first. POST /campaigns/preview takes the same body as a save and answers {url, problems}. It saves nothing, and name may be left out. problems lists every {field, detail} found, and url is null while there is one.
  2. Save it with POST /campaigns. The answer is the campaign with its url.
Save a newsletter campaign
curl -X POST "https://api.coritan.com/api/v1/orgs/acme/campaigns" \
  -H "Authorization: Bearer $CORITAN_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"name": "Summer launch", "base_path": "/pricing", "utm_source": "newsletter", "utm_medium": "email", "utm_campaign": "summer-launch", "coupon_code": "SAVE10"}'
JSON
{
  "id": 14,
  "name": "Summer launch",
  "base_path": "/pricing",
  "utm_source": "newsletter",
  "utm_medium": "email",
  "utm_campaign": "summer-launch",
  "utm_term": null,
  "utm_content": null,
  "coupon_code": "SAVE10",
  "notes": null,
  "archived": false,
  "archived_at": null,
  "url": "https://shop.example.com/pricing?utm_source=newsletter&utm_medium=email&utm_campaign=summer-launch&coupon=SAVE10",
  "created_by_user_id": 7,
  "created_at": "2026-09-21T12:00:00Z",
  "updated_at": "2026-09-21T12:00:00Z"
}

A value that does not fit answers 422 with detail, the sentence to show, and field, the input it belongs to:

JSON
{ "detail": "The page cannot start with //, which would name another site.", "field": "base_path" }

Change, archive or delete a campaign

Section titled Change, archive or delete a campaign

PATCH /campaigns/{campaign_id} changes the fields you send, by the same rules as a save. A null or empty utm_term, utm_content, coupon_code or notes clears it. Saving what is already there changes nothing and writes no audit entry.

Send {"archived": true} to archive a campaign and {"archived": false} to bring it back. An archived campaign keeps its link and its results, and GET /campaigns?status=archived lists it. Deleting with DELETE /campaigns/{campaign_id} removes the saved link only. The visits and orders that carried its words stay in your reports, and the word then appears under unmatched in the results.

GET /campaigns lists the campaigns, newest first. It takes status (all, active or archived), q (text in the name or the campaign word), limit (1 to 200, 100 by default) and offset. count is how many match and max_campaigns is 500.

Read what a campaign brought in

Section titled Read what a campaign brought in

GET /campaigns/results reads every saved campaign over a period.

Parameter What it sets
from and to The first and last day, YYYY-MM-DD, both included, in your brand's time zone. The last 30 days when left out. A period is at most 366 days.
currency A three letter code. Orders and revenue count in this one currency. When left out, it is the currency with the most revenue in the period, or your brand's own currency when there were no orders.
limit The rows in unmatched, 1 to 200 and 20 by default. Every saved campaign is always listed.

A campaign owns the visits, sign-ups and orders whose utm_campaign is its own word, ignoring capitals. The figures are the ones the acquisition report uses:

  • A visit is a stored session of your storefront with bots left out. We keep sessions for 180 days, so visits_complete is false when the period starts earlier. A stored session keeps its campaign word to 100 characters, so a longer word matches the first 100 characters of a visit's word.
  • A sign-up is a customer made through the storefront whose last visit named the campaign.
  • An order is a paid storefront order that is not free. Revenue is the invoice total without tax, in the minor units of currency, such as cents. Money is never added across currencies, and currencies names every currency with an order in the period.
  • new_customers counts orders that are the customer's first paid order. conversion_rate_bp is orders over visits in basis points, so 123 is 1.23 percent.
JSON
{
  "from": "2026-09-01",
  "to": "2026-09-30",
  "zone": "Europe/Berlin",
  "currency": "USD",
  "currencies": ["USD", "EUR"],
  "visits_complete": true,
  "campaigns": [
    {
      "id": 14, "name": "Summer launch", "utm_campaign": "summer-launch", "archived": false,
      "visits": 4, "signups": 3, "orders": 2, "new_customers": 1, "revenue_minor": 3000, "conversion_rate_bp": 5000
    }
  ],
  "unmatched": [
    {
      "campaign": "partner-push",
      "visits": 2, "signups": 1, "orders": 1, "new_customers": 1, "revenue_minor": 3000, "conversion_rate_bp": 5000
    }
  ]
}

unmatched lists the campaign words of the period that no saved campaign names, the biggest first. It shows traffic you tagged without saving a campaign.

GET /campaigns/{campaign_id}/results reads one campaign. It takes from, to and currency too, and compare (previous by default, or none). The answer holds the campaign and totals, with the same six figures and previous, the figures for the same number of days just before the period (or the whole month before, for a calendar month). previous is null when compare is none.

The campaign appears in the list with its url, and its results show the visits and orders that arrived through that link. The change is in the audit log as campaign.created, campaign.updated or campaign.deleted, with the campaign's name and, for an update, the names of the fields that changed.

409 with field name
Another campaign has this name, in any capitals. Pick another name or change the other campaign.
409 with no field, and the sentence names 500
The organization already keeps 500 campaigns. Delete or reuse one.
422 with field coupon_code
Your organization has no coupon with that code. A coupon of another organization does not count. Make the coupon first.
422 with field base_path
The page must be a path on your storefront. Write it with a leading /, and write a space as %20.
A campaign shows no visits
The campaign word in the visits must equal utm_campaign, ignoring capitals. Open the link and check its address. The period must also cover the visits, and sessions older than 180 days are gone.
The url has no address in front of it
We know no address for your storefront, so we give the path and query alone. Set a custom domain, or add your storefront's address in front of the link.
A campaign shows orders in the wrong currency
Orders count only in currency. Read the other currency with currency=EUR, for example.

The staff console's routes under /api/v1/orgs/{org_slug}/staff/marketing/campaigns answer the same, for the Billing, Admin and Owner roles, and platform staff have the same routes for any organization in the admin console. All of them need an access token, sent as Authorization: Bearer $CORITAN_TOKEN.

Operation What it does
GET /campaigns The campaigns, newest first, with url and archived.
POST /campaigns Save a campaign.
POST /campaigns/preview Check a body and build its url, saving nothing.
PATCH /campaigns/{campaign_id} Change a campaign, or archive it with archived.
DELETE /campaigns/{campaign_id} Delete a campaign.
GET /campaigns/results Every saved campaign's figures, and unmatched.
GET /campaigns/{campaign_id}/results One campaign's figures beside the period before.

API operations on this page

MethodPathWhat it does
GET/api/v1/orgs/{org_slug}/campaignsList the campaign links
POST/api/v1/orgs/{org_slug}/campaignsSave a campaign link
POST/api/v1/orgs/{org_slug}/campaigns/previewCheck a campaign link and build its address without saving it
GET/api/v1/orgs/{org_slug}/campaigns/resultsRead what each campaign brought in
GET/api/v1/orgs/{org_slug}/campaigns/{campaign_id}/resultsRead what one campaign brought in
PATCH/api/v1/orgs/{org_slug}/campaigns/{campaign_id}Change or archive a campaign link
DELETE/api/v1/orgs/{org_slug}/campaigns/{campaign_id}Delete a campaign link
GET/api/v1/orgs/{org_slug}/staff/marketing/campaignsList the campaign links
POST/api/v1/orgs/{org_slug}/staff/marketing/campaignsSave a campaign link
POST/api/v1/orgs/{org_slug}/staff/marketing/campaigns/previewCheck a campaign link and build its address without saving it
GET/api/v1/orgs/{org_slug}/staff/marketing/campaigns/resultsRead what each campaign brought in
GET/api/v1/orgs/{org_slug}/staff/marketing/campaigns/{campaign_id}/resultsRead what one campaign brought in
PATCH/api/v1/orgs/{org_slug}/staff/marketing/campaigns/{campaign_id}Change or archive a campaign link
DELETE/api/v1/orgs/{org_slug}/staff/marketing/campaigns/{campaign_id}Delete a campaign link