Campaigns
This page is the API reference for developers reading or writing campaigns, their assets, tasks, budgets and attribution.
The Campaigns app holds the campaigns of a shop. A campaign is a named marketing effort with a window, a goal and an owner. Promotions, mailings, journeys, forms and pages are attached to it, and the app shows everything dated on one calendar. It also records what each campaign achieved: touches, attribution facts that credit an order to a campaign, and the budget and ad spend booked against it.
Objects
| Object | What it holds |
|---|---|
| Campaign | code (unique per tenant), name, description, status (draft, active, completed or archived), effective_state (draft, planned, running, ended or archived, kept by the app), starts_at, ends_at, goal_metric, goal_value, currency, owner_id, utm_campaign, and the six trash fields. |
| Asset | One row per promotion, mailing, journey, form, page, page library item or ad campaign link that a campaign holds: the app, the entity, the id, a label and whether the asset carries the campaign. The owning app keeps the asset itself. |
| Task | campaign_id, title, assignee, due_at, done_at and notes. |
| Touch | One recorded contact between a campaign and a buyer before an order, or an outcome of one (a redeemed promotion, a new subscription or a new lead), with its kind, ref, contact_id and market. |
| Attribution fact | One order, or part of an order, credited to a campaign through one or more touches, with the flag is_first_touch. Read-only. |
| Budget line | campaign_id, line, planned, spent, currency and notes. A line that the ads app books day by day is fed from ad spend. |
| Spend day | One day of ad spend on one platform, booked into the campaign's budget line for that platform. |
Base path: https://api.revenexx.com/v1/campaigns.
Scopes
A key calls these routes with the capability key of each route. The App's manifest names one per operation, in the form campaigns.<group>.<operation>, and the API keys page explains that a key holds capability keys or a namespace such as campaigns.*. The groups are campaigns, assets, tasks, attribution, touches, budgets, spend, calendar and vocabularies. The scope names are listed in the tables below. A call that needs a scope the key does not hold is refused with 403.
Campaigns
| Operation | Scope | What it does |
|---|---|---|
GET /v1/campaigns/campaigns | campaigns.campaigns.list | Lists the campaigns of the tenant. The trash is left out unless trashed asks for it. |
POST /v1/campaigns/campaigns | campaigns.campaigns.create | Writes a campaign down. |
GET /v1/campaigns/campaigns/{id} | campaigns.campaigns.get | Reads one campaign. |
PUT /v1/campaigns/campaigns/{id} | campaigns.campaigns.update | Corrects a campaign. |
DELETE /v1/campaigns/campaigns/{id} | campaigns.campaigns.delete | Moves a campaign into the trash. A second DELETE on the same campaign deletes it for good, unless a blocker holds it (see below). |
POST /v1/campaigns/campaigns/{id}/restore | campaigns.campaigns.restore | Takes a campaign back out of the trash. |
POST /v1/campaigns/campaigns/trash/empty | campaigns.campaigns.empty-trash | Deletes every trashed campaign that nothing blocks, for good. The answer lists deleted and blocked (each blocked one with its reason); a blocked campaign stays in the trash. |
POST /v1/campaigns/campaigns/trash/purge | campaigns.campaigns.purge | Deletes the campaigns that have been in the trash longer than the tenant's trash retention. It is the nightly purge, callable by hand. |
POST /v1/campaigns/campaigns/{id}/attach | campaigns.campaigns.attach | Attaches one asset to the campaign. |
POST /v1/campaigns/campaigns/{id}/detach | campaigns.campaigns.detach | Releases one asset from the campaign. |
GET /v1/campaigns/campaigns/{id}/performance | campaigns.campaigns.performance | Reads what the campaign achieved against its goal, under one attribution model. |
POST /v1/campaigns/campaigns/{id}/links | campaigns.campaigns.links | Builds a URL tagged with the campaign's UTM values, and writes the UTM value into the campaign when it has none yet. |
POST /v1/campaigns/campaignsbody: requiredcodeandname. Optional:description,status,starts_at,ends_at,goal_metric,goal_value,currency,owner_id,utm_campaign,tags,metadata,external_id,external_refs,source_dataandsource_synced_at. The app setseffective_stateitself. A value sent for it is dropped.- Create and update
400rules (PUTis checked the same way, on the merged campaign):goal_metricis one ofrevenue,orders,redemptions,leads,subscriptionsorclicks;goal_valueis a number above zero and needs agoal_metric;ends_atis not beforestarts_at;utm_campaignstarts with a lower-case letter or digit and then holds only lower-case letters, digits, hyphens, dots and underscores;tagsis a list of strings. PUT /v1/campaigns/campaigns/{id}body: the same fields as the create, all optional.- Figures on a campaign: read-only, mirrored from the figures of the performance route under the tenant's default attribution model (a
modelpassed to the performance route does not change them). A value sent for them on a create or update is dropped.orders_count(integer): orders credited under the tenant's attribution model.revenue(number): revenue credited under the tenant's attribution model, returns netted out.roas(number or null): revenue divided by spend; null while nothing was spent.goal_percent(number or null): how far the campaign got towards its goal, 0 to 100 and beyond; null without a goal.
- Filters on
GET /v1/campaigns/campaigns:effective_state(for examplerunning, for what runs now),utm_campaign(to find the campaign a tracked landing page names), and exact matches onid,code,name,description,status,owner_id,goal_metric,currency,orders_countandexternal_id.trashedisexclude(the default),onlyorinclude.limittakes 1 to 200 (default 50), withoffsetandorder(a column, optionally.ascor.desc). - Create and update errors:
409withcode_takenwhen another campaign has thecode,utm_takenwhen theutm_campaignvalue belongs to another campaign, and409within_trashwhen the value belongs to a campaign in the trash (restore that one instead). - Writes to a trashed campaign:
PUT,attach,detachandlinksanswer409within_trashand name the restore route. A body that sends a trash column such asdeleted_atorpurge_atis400: those are set by deleting and restoring. DELETEerrors:404when no campaign has the id. A campaign with attribution facts or touches is not moved to the trash:409withtrash_blockedand the blockercampaign_in_useindetails.blockers. Archive it instead. A campaign that still holds assets goes into the trash, but the secondDELETErefuses it with409delete_blockedand the blockercampaign_has_assetsuntil they are detached. Deleting for good is refused with403for a storefront contact.- Trash empty and purge: a call from a storefront contact is
403withpurge_not_allowed. A campaign that a blocker still holds stays in the trash, and the answer names its reason. The purge takes the campaigns that have been in the trash longer than the tenant's trash retention, which is 90 days unless the tenant set another value. When the retention cannot be read, the purge deletes nothing and answersskipped. restore: clears the trash marks and leaves the campaign's other fields as they were. A campaign that is not in the trash is409withnot_in_trash.attachbody: name one asset with one ofpromotion_id,mailing_id,journey_id,form_id,page_id,library_item_idorad_campaign_link_id, or with the three fieldsasset_app,asset_entityandasset_id.asset_entityis optional and defaults from the app (adsgivesad_campaign_links).labelis optional. Attaching an asset that is already attached to this campaign answers the existing row.- What
attachdoes: a promotion, mailing, journey or form is claimed by writing the campaign's id into its owncampaign_ref. A page, a page library item or an ad campaign link is recorded here only. attacherrors:400withunknown_asset_kindwhen the app and entity are not something a campaign can hold.404when the owning app has no such asset.409withasset_claimedwhen another campaign holds the asset, andcampaign_archivedwhen the campaign is archived.502withasset_app_unavailablewhen the owning app does not answer or refuses the write; then nothing is attached. A new attachment answers201, an existing one200.detacherrors:404when the campaign or the asset is unknown.502withasset_app_unavailablewhen the owning app does not answer. The asset stays attached in that case.performancequery:model, eitherfirst_touchorlast_touch. Without it, the tenant's default model applies. A campaign is credited under one model at a time. The answer holds revenue and orders under that model, with returns netted out, redemptions, clicks, impressions, subscriptions and leads, the goal progress, the budget planned and spent with return on ad spend, and per attached asset its revenue, orders, clicks and redemptions.400whenmodelis another value,404when no campaign has the id. A campaign in the trash can still be read.linksbody: requiredurlandmedium. Optional:source,contentandterm. The app addsutm_source(the default comes from the app settings),utm_mediumandutm_campaign, and optionallyutm_contentandutm_term. UTM values that the URL already carries are replaced. A campaign without a UTM value gets its code slug the first time.linkserrors:409withutm_campaign_missingwhen the campaign has no UTM value and its code gives none, or when it has no UTM value and its code slug already belongs to another campaign. Setutm_campaignfirst.
Assets
| Operation | Scope | What it does |
|---|---|---|
GET /v1/campaigns/assets | campaigns.assets.list | Lists the assets that campaigns hold. |
GET /v1/campaigns/assets/{id} | campaigns.assets.get | Reads one asset row. |
POST /v1/campaigns/assets/{id}/detach | campaigns.assets.detach | Releases the asset that an asset row names. The same as detaching from the campaign, addressed by the asset row. |
- Filters on
GET /v1/campaigns/assets:campaign_idfor one campaign, orasset_appwithasset_idfor the campaign that holds one asset. Alsoasset_entity,label,claimandadded_by. - Writes: an asset row is created only by
attachon a campaign. Both detach routes, the campaign's and the asset row's, remove it. - Detach: clears the
campaign_refon the asset, if it still names this campaign, and removes the asset row.
Tasks
| Operation | Scope | What it does |
|---|---|---|
GET /v1/campaigns/tasks | campaigns.tasks.list | Lists the campaign tasks. |
POST /v1/campaigns/tasks | campaigns.tasks.create | Adds a task to a campaign. |
GET /v1/campaigns/tasks/{id} | campaigns.tasks.get | Reads one task. |
PUT /v1/campaigns/tasks/{id} | campaigns.tasks.update | Corrects a task. |
DELETE /v1/campaigns/tasks/{id} | campaigns.tasks.delete | Deletes a task. |
POST /v1/campaigns/tasks/{id}/complete | campaigns.tasks.complete | Ticks a task off. |
POST /v1/campaigns/tasks/{id}/reopen | campaigns.tasks.reopen | Opens a task again. |
POST /v1/campaigns/tasksbody: requiredcampaign_idandtitle. Optional:assignee,due_at,done_atandnotes.- Filters on
GET /v1/campaigns/tasks:campaign_id,title,notesandassignee, each an exact match. complete: stampsdone_atwith the current time. A task that is already done keeps its first stamp.- Calendar: the due dates of tasks appear on the calendar.
Attribution
| Operation | Scope | What it does |
|---|---|---|
GET /v1/campaigns/attribution-facts | campaigns.attribution.list | Lists the attribution facts, the ledger of credited orders. |
GET /v1/campaigns/attribution-facts/{id} | campaigns.attribution.get | Reads one attribution fact. |
POST /v1/campaigns/attribution/sweep | campaigns.attribution.sweep | Records what the orders and redemptions of the last two days owe campaigns. |
- Read-only: no caller writes a fact directly. Facts are written from orders, redemptions and touches, and the sweep records the ones they owe.
- Filters on
GET /v1/campaigns/attribution-facts:campaign_id,order_id,order_number,contact_id,organization_id,currency,market,touch_kind,touch_refandis_first_touch.limit,offsetandorderpage and sort the list. - Returns: a return raises the fact's
amount_returned;amountstays. A fact is never deleted. sweep: the hourly schedule, callable by hand. It reads the committed redemptions of attached promotions and the orders placed in the last 48 hours from their own apps, and records facts under the same keys that the event handlers use. It also moves each campaign'seffective_statewhen its window opens or closes.
Touches
| Operation | Scope | What it does |
|---|---|---|
GET /v1/campaigns/touches | campaigns.touches.list | Lists the recorded touches. |
POST /v1/campaigns/touches | campaigns.touches.ingest | Records on-site impressions and clicks and ad clicks from the storefront. |
GET /v1/campaigns/touches/{id} | campaigns.touches.get | Reads one touch. |
POST /v1/campaigns/touchesbody:touches, a list of one to 500 items. Each item haskind(onsite_impression,onsite_clickorad_click) andcampaign_id, and optionallyat,contact_id,session_key,market,keyandref. Each item is judged on its own. The answer countsrecordedandduplicatesand names eachrejecteditem byindexandreason.- Per-item reasons: an item that is not accepted is named in
rejectedwith one ofunknown_kind,unknown_campaign,invalid_timeorinvalid_contact. An item with akeythat is already recorded is counted as a duplicate. - Filters on
GET /v1/campaigns/touches:campaign_id,kind,contact_id,ref,order_id,market,session_keyandtouch_key. - Read-only apart from the ingest: a touch is never edited.
Budgets
| Operation | Scope | What it does |
|---|---|---|
GET /v1/campaigns/budgets | campaigns.budgets.list | Lists the budget lines. |
POST /v1/campaigns/budgets | campaigns.budgets.create | Adds a budget line. |
GET /v1/campaigns/budgets/{id} | campaigns.budgets.get | Reads one budget line. |
PUT /v1/campaigns/budgets/{id} | campaigns.budgets.update | Corrects a budget line. |
DELETE /v1/campaigns/budgets/{id} | campaigns.budgets.delete | Deletes a budget line. |
POST /v1/campaigns/budgetsbody: requiredcampaign_idandline. Optional:currency,planned,spentandnotes.- Filters on
GET /v1/campaigns/budgets:campaign_id,line,currency,source,platformandnotes. - Errors:
409withline_takenwhen the campaign already has a line of that name.409withbudget_line_from_adswhen a line that the ads app books day by day is edited or deleted by hand, or when a body sendssourceasadsor sends aplatform.
Ad spend
| Operation | Scope | What it does |
|---|---|---|
GET /v1/campaigns/spend | campaigns.spend.list | Lists the booked days of ad spend. |
POST /v1/campaigns/spend | campaigns.spend.import | Books daily ad spend into the campaign budgets. |
GET /v1/campaigns/spend/{id} | campaigns.spend.get | Reads one booked day. |
POST /v1/campaigns/spendbody:rows, a list of one to 500 rows. Each row books one day of one platform into the campaign's budget line for that platform. Each row hascampaign_id,date(YYYY-MM-DD),spend(zero or more),currencyandplatform. Optional:impressionsandclicks, whole numbers.- The budget line: each platform books into its own line,
ads:<platform>, with:<currency>added when the row's currency differs from the campaign's. The line is created on first use. Booking the same campaign, platform and day again replaces the earlier booking for that day. - Answer and per-row reasons: the answer counts
bookedandreplacedand names eachrejectedrow byindexandreason, one ofnot_an_object,unknown_campaign,invalid_date,invalid_spend,invalid_currencyorinvalid_platform. - Filters on
GET /v1/campaigns/spend:campaign_id,budget_id,platform,spend_date,currency,impressionsandclicks.
Calendar
| Operation | Scope | What it does |
|---|---|---|
GET /v1/campaigns/calendar | campaigns.calendar.read | Reads the dated activity of the campaigns that overlap a range, at most 100 campaigns. Archived campaigns appear only with status=archived. type is a comma list of campaign, mailing, promotion, journey, page and task. |
- Range:
fromandtoare required dates. A bare date astocovers the whole of that day. Atobeforefromis400. A range can span at most 400 days, or the call is400. - Filters:
type,market,statusandowner_id. - What it holds: campaign windows and due tasks from this app. The send times of mailings, the windows of promotions, the live periods of automations and the publish dates of pages are read from their own apps on every call and are not stored here.
sources: the answer says which app answered for each part.
Vocabularies
| Operation | Scope | What it does |
|---|---|---|
GET /v1/campaigns/vocabularies | campaigns.vocabularies.list | Lists the closed vocabularies this app enforces. |
GET /v1/campaigns/vocabularies/{name} | campaigns.vocabularies.get | Reads one vocabulary with its values and labels. 404 when there is no such vocabulary. |