The Event editor lets a partner correct an event’s metadata after it’s been ingested and re-submit it to the Customer Representation model.
This page reads and writes on the CR API — a different host than the main /v3 API. Get this wrong and you get a 404 or a confusing 401.

What you’ll build

  • A form pre-filled with the event’s current values.
  • A submit handler that PUTs a formatted payload to the CR API.
  • Logic that conditionally appends itc_retry=true to trigger a recommendation refresh.

Prerequisites

  • An eid.
  • The partner’s product / access tier (drives event_type and the partner_product query param). Read from your client store; the reference webapp reads partnerDetails.product from browser storage.
  • User has Lookout or Wave access.

The mount call chain

#MethodPathHostPurpose
1GET/events/{eid}CR APIRaw event payload (includes lineups_raw, description).
2GET/events/{eid}/campaignsmain APIDetermines isPublished (any campaign with published === true). Gates whether itc_retry is ever sent.

Submit

The reference FE uses PUT, not POST:
PUT [cr_api] /events/{eid}?event_type={prisma|fdlive}&partner_product={product}[&itc_retry=true]
URL paramValuesWhen
event_typeprisma or fdliveAlways. Derived from the partner’s access tier and the event’s category (see below).
partner_productone of the partner’s registered productsAlways. Mismatch ⇒ 422.
itc_retrytrueOnly appended when !isPublished && hasRetryRelevantChanges. Omitted entirely otherwise (not sent as false).

Two separate mechanisms: dirty-state vs itc_retry

These are unrelated — document and implement them separately. A. Form dirty-state (fieldsChanged / hasChange): UI-only. Enables the Save button and prevents empty submits. Never sent to the backend. B. Recommendation-refresh flag (itc_retry): submit-time logic for CR recommendation recalculation. Sent only when both hold:
  • !isPublished — the event has no published campaign (from GET /events/{eid}/campaigns); and
  • hasRetryRelevantChanges(...) — a recommendation-relevant field changed.
“Force a refresh by sending itc_retry=true with an unchanged payload” is not supported in the reference FE. And if the event already has a published campaign, itc_retry is never sent, even when fields change.

Which field changes count as recommendation-relevant

The reference FE compares (note nametitle mapping, and lineups via lineups_raw):
// Recommendation-relevant comparison (hasRetryRelevantChanges)
const RECO_RELEVANT = [
  "name",        // event title (FE field is `name`, mapped from `title`)
  "description",
  "category",
  "city",
  "hall",
  "venue",
  "lineups",     // compared against lineups_raw: artist_name + artist_type
];
// NOT compared for itc_retry: setlists, capacity, special_ad_category

The PUT payload is formatted, not the raw CR object

The FE builds the body via formatData(...) — it does not echo back the raw CR payload. Key field differences from a naive mapping:
{
  "name": "Concert at Olympiahalle (rescheduled)",
  "description": "...",
  "category": "concert",
  "venue": "Olympiahalle",
  "hall": "Main Hall",
  "city": "München",
  "lineups": [
    { "artist_name": "Headliner", "artist_type": "main" }
  ],
  "setlists": [
    { "artist_name": "Headliner", "track": "Opener" }
  ],
  "price_category": 75,
  "status": "RUNNING",
  "special_ad_category": []
}
FieldShape
Event titlename (not title)
Lineups[{ artist_name, artist_type }] (not { name, starts_at })
Setlists[{ artist_name, track }]
Pricescalar price_category (not nested { min, max })
statusalways "RUNNING" in the payload
special_ad_categorydefault form → []; Prisma form → values from the special tab (always an array)

Deriving event_type

Don’t expose event_type as a UI toggle. The reference FE derives it from:
  • hasPartialServiceAccessTier() — true when the partner product is PRISMA or FD_FULL_SERVICE, or the editor was opened from Wave (location.state?.from === 'wave') → prisma
  • otherwise → fdlive
(The event’s category also feeds the wave/product branch.)

Reference implementation

import { fdCr } from "./fd-cr";
import { fd }    from "./fd";

export async function loadEvent(eid: string) {
  const [raw, campaigns] = await Promise.all([
    fdCr.get(`/events/${eid}`),                 // CR API — raw payload
    fd.get(`/events/${eid}/campaigns`),         // main API — for isPublished
  ]);
  const isPublished = (campaigns.items ?? []).some((c: any) => c.published);
  return { raw, isPublished };
}

export async function saveEvent(opts: {
  eid: string;
  eventType: "prisma" | "fdlive";
  partnerProduct: string;
  isPublished: boolean;
  hasRetryRelevantChanges: boolean;
  body: FormattedEvent;          // built via your formatData()
}) {
  const q = new URLSearchParams({
    event_type:      opts.eventType,
    partner_product: opts.partnerProduct,
  });
  // Only append itc_retry when not published AND a relevant field changed.
  if (!opts.isPublished && opts.hasRetryRelevantChanges) {
    q.set("itc_retry", "true");
  }
  return fdCr.put(`/events/${opts.eid}?${q}`, opts.body);
}

Post-save redirect

After a successful CR save, the reference FE:
  1. Performs another CR GET /events/{eid}, waits ~1s, then uses history.replace (not push) so /events/edit/{eid} is not left in browser history.
  2. Redirects to the list with the side drawer open:
    • Lookout access: /events?drawer-open=true&event-id={eid}&tab=0
    • Wave-only: /campaigns?drawer-open=true&event-id={eid} (the Wave example also appends tab=0 in current FE)
  3. In the drawer, the event is loaded via the main API GET /events/{eid}, not CR.
tab=0 resets to the first Event Detail tab (insights). The choice of /events vs /campaigns depends only on Lookout permission, not on category or where the editor was opened from.

Gotchas

The save is cr_api.put(/events/{eid}?...). POST is wrong.
name not title; lineups artist_name/artist_type; scalar price_category; status: "RUNNING" always.
Append &itc_retry=true only when !isPublished && hasRetryRelevantChanges. Never send itc_retry=false.
Form dirty-state is UI-only; itc_retry is a separate submit-time decision. Don’t conflate them.
The CR API 422s on a mismatch. Derive event_type from access tier + category, not a UI toggle.