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.
In the dashboard
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.
Before you begin
Section titled Before you begin- 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.
Set it up in your dashboard
Section titled Set it up in your dashboardOwners, 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.
- In the dashboard, open the organization, then Growth, then Campaigns.
- 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. - 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.
- 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.
- 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.
What a campaign holds
Section titled What a campaign holds| 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.
The link we build
Section titled The link we buildEvery 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.
Save a campaign
Section titled Save a campaign- Check the link first.
POST /campaigns/previewtakes the same body as a save and answers{url, problems}. It saves nothing, andnamemay be left out.problemslists every{field, detail}found, andurlisnullwhile there is one. - Save it with
POST /campaigns. The answer is the campaign with itsurl.
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"}'
{
"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:
{ "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 campaignPATCH /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 inGET /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_completeisfalsewhen 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, andcurrenciesnames every currency with an order in the period. new_customerscounts orders that are the customer's first paid order.conversion_rate_bpis orders over visits in basis points, so 123 is 1.23 percent.
{
"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.
Result
Section titled ResultThe 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.
Troubleshooting
Section titled Troubleshooting409withfieldname- Another campaign has this name, in any capitals. Pick another name or change the other campaign.
409with nofield, and the sentence names 500- The organization already keeps 500 campaigns. Delete or reuse one.
422withfieldcoupon_code- Your organization has no coupon with that code. A coupon of another organization does not count. Make the coupon first.
422withfieldbase_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
urlhas 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 withcurrency=EUR, for example.
With the API
Section titled With the APIThe 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. |
Related
Section titled RelatedNext steps
Section titled Next steps- Read the acquisition report to see every channel, not only your tagged campaigns.
- Connect ad accounts and see what ads return to set each campaign's cost against its orders.
- Send purchases to your ad platforms so the platforms count the sales your links bring.