# Affilyflow Docs
# Alt om at sætte Affilyflow op
> Opsætning af sporing for butikker, teknisk reference for udviklere og API til affiliates – samlet et sted og skrevet ud fra, hvordan produktet faktisk virker.
## Find din vej
- **Butik på en færdig platform** (Shopify, WooCommerce, Smartweb, DanDomain, Shoporama, Webflow): vælg platformen i menuen. Shopify-appen og WooCommerce-pluginnet klarer det meste selv.
- **Butik på specialbygget webshop** (fx Laravel, PHP eller en egen løsning): start med [Specialbygget webshop](https://www.affilyflow.com/docs/butik/specialbygget). Sender jeres site allerede købet til Google Analytics, er det en linje i alt – ingen feltnavne at finde.
- **Abonnementer og betalinger i Stripe:** [Stripe](https://www.affilyflow.com/docs/butik/stripe).
- **Udvikler:** [track.js](https://www.affilyflow.com/docs/reference/track-js), [salgslinjen](https://www.affilyflow.com/docs/reference/salgslinje), [Google Analytics-vejen](https://www.affilyflow.com/docs/reference/google-analytics) og [konverterings-API'et](https://www.affilyflow.com/docs/reference/konverterings-api).
- **Affiliate:** [links og sub-ID](https://www.affilyflow.com/docs/affiliate/links-og-sub-id), [REST API](https://www.affilyflow.com/docs/affiliate/rest-api) og [MCP til AI-assistenter](https://www.affilyflow.com/docs/affiliate/mcp).
## Sådan hænger det sammen
1. En affiliate deler et link. Klikket går gennem Affilyflow og lander på butikken med et klik-id i adressen (`?afid=c_…`).
2. Butikkens sporing husker klikket – Shopify-pixlen, WooCommerce-pluginnet eller `track.js`.
3. Kunden køber. Salget meldes til Affilyflow med ordrenummer og beløb og kobles til klikket.
4. Provisionen er låst på klikket og bliver godkendt, når godkendelsesperioden er gået, medmindre butikken afviser salget.
Læs hele forløbet i [Sådan virker sporingen](https://www.affilyflow.com/docs/start/sadan-virker-sporingen).
## Test, før I går live
Hver butik har et **testlink** under *Opsætning*. Et testkøb gennem det giver ingen provision og intet gebyr, og *Opsætning* viser klikket, ordrenummeret og beløbet med det samme – også hvordan beløbet blev regnet. Se [Test og fejlfinding](https://www.affilyflow.com/docs/butik/test-og-fejlfinding).
> [!TIP]
> Bruger I en AI-kodeassistent som Claude Code eller Cursor? Hver side findes også som Markdown (tilføj `.md` til adressen), og hele dokumentationen ligger i `/llms-full.txt`.
Kilde: https://www.affilyflow.com/docs
---
# Sådan virker sporingen
> Fra et affiliatelink bliver klikket til et salg med låst provision – og hvad der sker undervejs.
## Klikket
Alle affiliatelinks går gennem Affilyflow (`https://app.affilyflow.com/go/…`). Her registreres klikket, og kunden sendes videre til butikken med et klik-id i adressen:
```text title="Kunden lander på butikken"
https://dinbutik.dk/vare/sko?afid=c_Go6eoYsoZA4i
```
Samtidig sker tre ting:
- **Provisionen låses på klikket.** Den sats, programmet havde, da kunden klikkede, er den, affiliaten får – også hvis butikken senere ændrer den.
- **Et besøg tæller som et klik.** Klikker samme besøgende flere gange på samme affiliates links til samme program inden for 30 minutter, tæller det som et klik. En affiliate, der klikker på sine egne links, tæller ikke.
- **Bots og falske klik tæller ikke.** Søgemaskiners og SEO-værktøjers crawlere, klik fra skyservere og unormalt mange klik fra samme besøgende sorteres fra. Kunden sendes stadig videre til butikken.
## Butikken husker klikket
Når kunden lander, gemmer butikkens sporing klik-id'et – hvordan afhænger af platformen:
| Platform | Hvordan klikket huskes |
|---|---|
| Shopify | Affilyflow-appens Web Pixel (ingen kode i temaet) |
| WooCommerce | Pluginnet gemmer klikket på butikkens server |
| Alle andre | `track.js` i sidens `
` gemmer klikket i en cookie og i browserens lager |
Klikket huskes i op til 90 dage. Hvor længe et klik giver provision, bestemmer programmets **cookie-vindue** (30 dage er standarden; 14–90 kan vælges). Et nyt besøg forlænger aldrig vinduet – det regnes altid fra klikket.
> [!NOTE]
> Safari og iPhone sletter cookies og lager sat af JavaScript efter cirka 7 dage uden besøg. Det gælder alle netværk, der sporer med et script. En løsning, hvor butikkens egen server gemmer klikket (som WooCommerce-pluginnet), holder hele vinduet.
## Salget
Når kunden køber, meldes salget til Affilyflow med ordrenummer og beløb:
- **Shopify:** pixlen melder købet af sig selv.
- **WooCommerce:** pluginnet melder ordren fra butikkens server – også refusioner.
- **Stripe:** webhooken melder betalingen, også fornyelser af abonnementer.
- **Specialbygget webshop og de danske platforme:** `track.js` læser salget fra Google Analytics-købet, fra en linje på kvitteringssiden eller fra platformens egen kode. Se [Specialbygget webshop](https://www.affilyflow.com/docs/butik/specialbygget).
Beløbet, provisionen regnes af, er **varerne efter rabat, uden moms og fragt**. Samme ordre tælles kun en gang, også hvis kunden genindlæser kvitteringen. Salg i en anden valuta omregnes til butikkens afregningsvaluta med dagens kurs, og kursen låses på salget.
## Godkendelse
Et nyt salg står som **afventer**. Butikken kan afvise det inden for programmets **godkendelsesperiode** (30, 45, 60 eller 90 dage – sat til butikkens returret), fx ved en returvare. Perioden låses på salget. Når den er gået, godkendes salget automatisk, og provisionen tæller med i affiliatens saldo.
Nogle salg godkendes aldrig af sig selv: salg, der skal vurderes af et menneske, fx hvis køberen ser ud til at være affiliaten selv.
## Testsalg
Butikkens **testlink** (under *Opsætning*) giver et klik fra Affilyflows egen testaffiliate. Et køb gennem det bliver et testsalg: det koster ingenting, tæller ingen steder og sender ingen beskeder – men det beviser, at hele vejen virker. Se [Test og fejlfinding](https://www.affilyflow.com/docs/butik/test-og-fejlfinding).
## Hvad vi gemmer
Om klikket gemmer vi tidspunkt, link, siden det kom fra (adresse uden parametre), land og en forkortet browserbeskrivelse (enhed, styresystem og browserens hovedversion). IP-adressen gemmes aldrig – misbrugstællere bruger en daglig nøgle, der ikke kan føres tilbage til adressen. Om salget gemmer vi ordrenummer, beløb, valuta og eventuelle varelinjer – aldrig kundens navn, e-mail eller adresse.
Kilde: https://www.affilyflow.com/docs/start/sadan-virker-sporingen
---
# Kom i gang som butik
> Fra ny konto til et program, affiliates kan finde – konto, pris, sporing og et testkøb.
En butik er klar til affiliates, når to ting er på plads: jeres hjemmeside er gemt, og sporingen kører. Indtil da er programmet skjult i kataloget. Denne side tager jer gennem det hele i rækkefølge.
## Før du starter
- Adressen på jeres webshop (fx `dinbutik.dk`).
- Adgang til webshoppens administration – eller en udvikler, I kan sende guiden til.
- Et par minutter til et testkøb i jeres egen shop.
## 1. Opret kontoen
Opret butikskontoen på store.affilyflow.com. Tilmeldingen spørger om:
- butikkens navn,
- webshoppens adresse – det er den, affiliates linker til, og den vi tjekker sporingen på,
- landet, hvor virksomheden er registreret – det afgør momsen på jeres fakturaer,
- e-mail og adgangskode, og at I accepterer vilkårene.
Bekræft derefter e-mailen. Linket i mailen udløber efter en time.
> [!NOTE]
> Lige nu får en ny butikskonto 250 kr. i startkredit på saldoen, når e-mailen er bekræftet. Kreditten bruges før jeres egne indbetalinger og kan ikke udbetales. Konti, som Shopify-appen opretter, får den ikke.
## 2. Sæt programmet op
Der er oprettet et program til jer fra start, med en cookie-periode på 30 dage. Åbn **Program** og gennemgå det:
- **Prisen.** Skriv, hvad affiliaten får, eller jeres samlede pris pr. salg – det ene regnes ud af det andet. Netværksgebyret er 20 % af den samlede pris, og affiliaten får de 80 %. En samlet pris på 20 % giver affiliaten 16 % og 4 % i gebyr.
- **Cookie-perioden.** Hvor længe efter et klik et køb tæller. Den kan sættes fra 14 til 90 dage; vi anbefaler 30. Perioden låses på klikket og håndhæves på vores server.
- **Godkendelsesperioden.** 30, 45, 60 eller 90 dage til at afvise et salg. Sæt den til jeres returret – se [Godkendelse og refusioner](https://www.affilyflow.com/docs/butik/godkendelse-og-refusioner).
- **Reglerne.** Betalt søgning, rabatkodesider og tilladte kanaler.
- **Beskrivelsen.** Det første, affiliates læser på jeres programside.
Gem programmet, når det passer – også selvom I beholder standarden. Det markerer opgaven som klaret under **Kom i gang**.
## 3. Sæt sporingen op
Åbn **Program → Opsætning**. Siden har altid tre trin: vælg platform, installer, test.
Første trin spørger **Hvor sælger du?** Vælg den platform, webshoppen kører på. Opsætning prøver selv at genkende platformen på jeres forside og markerer den med **Genkendt**.
| Jeres shop | Guide |
|---|---|
| Shopify | [Shopify](https://www.affilyflow.com/docs/butik/shopify) |
| WooCommerce | [WooCommerce](https://www.affilyflow.com/docs/butik/woocommerce) |
| Smartweb, HostedShop eller den nye Dandomain Webshop | [Smartweb og HostedShop](https://www.affilyflow.com/docs/butik/smartweb) |
| DanDomain Classic | [DanDomain Classic](https://www.affilyflow.com/docs/butik/dandomain-classic) |
| Shoporama | [Shoporama](https://www.affilyflow.com/docs/butik/shoporama) |
| En webshop, der er udviklet til jer | [Specialbygget webshop](https://www.affilyflow.com/docs/butik/specialbygget) |
| Stripe Checkout eller Payment Links | [Stripe](https://www.affilyflow.com/docs/butik/stripe) |
| Webflow, Wix eller noget helt tredje | Vælg **Andet**. Webflow har sin egen guide: [Webflow](https://www.affilyflow.com/docs/butik/webflow) |
> [!TIP]
> Tager I betaling med Stripe, men kører shoppen på Shopify, WooCommerce eller en af de danske platforme? Vælg platformen. Stripe er kun det rigtige valg, når kunderne betaler i Stripe Checkout eller via Payment Links.
Er det ikke jer, der skal sætte koden ind, så tryk **Send til udvikler**. Vi mailer hele guiden med færdige kodestumper til den adresse, I skriver.
Vil I forstå, hvad der sker fra klik til salg, før I går i gang, så læs [Sådan virker sporingen](https://www.affilyflow.com/docs/start/sadan-virker-sporingen).
## 4. Test med et testkøb
Tryk **Åbn testlink** under testtrinnet, og læg en ordre i den fane, der åbner. Bliv på Opsætning: siden følger med og viser testklikket og købet med ordrenummer og beløb. Derefter spørger den, om beløbet passer.
- Et testkøb giver ingen provision eller gebyr og tæller ikke med i jeres tal.
- Køb i samme browser, som testlinket åbnede i.
- Annuller ordren bagefter i jeres shop.
Shopify-butikker bruger testlinket i Affilyflow-appen, og Stripe sender ikke testbetalinger videre til os – se [Stripe](https://www.affilyflow.com/docs/butik/stripe). Går noget galt, så se [Test og fejlfinding](https://www.affilyflow.com/docs/butik/test-og-fejlfinding).
## Hvornår programmet bliver synligt
Programmet står i kataloget, på affilyflow.com og i produktsøgningen, når hjemmesiden er gemt, og sporingen kører. "Kører" betyder:
| Sporing | Kører, når |
|---|---|
| Shopify-appen | appen har meldt, at pixlen er aktiv |
| WooCommerce-pluginnet | vi har fået det første signal fra pluginnet |
| Stripe | vi har modtaget den første hændelse fra jeres webhook |
| Script på eget website | scriptet er fundet på forsiden, og salgskoden har meldt et køb – et testkøb tæller |
Et program, der betaler pr. lead, kræver kun scriptet.
Oversigten viser, om programmet er synligt, og hvad der mangler. Affiliates, der allerede er tilmeldt, beholder deres links, også mens programmet er skjult.
## Kom i gang-listen
På **Oversigten** står **Kom i gang** med det, der mangler:
1. Tilføj din hjemmeside
2. Sæt sporingen op
3. Gennemgå dit program
4. Beskriv butikken og kunderne
5. Udfyld faktureringsoplysninger
6. Sæt penge på saldoen
Under **Undervejs** står milepælene: første affiliate og første salg. Listen kan skjules; statuspanelet viser stadig, hvad der mangler.
## Saldo og faktura
Provision og gebyr – for danske butikker også momsen – trækkes fra saldoen, når et salg godkendes. Sæt et beløb ind under **Økonomi**, før de første salg godkendes.
Godkendte salg faktureres samlet en gang om måneden. Udfyld navn, adresse og land under **Indstillinger → Fakturering** i god tid: en faktura kan ikke rettes, efter den er udstedt.
Kilde: https://www.affilyflow.com/docs/start/kom-i-gang-butik
---
# Kom i gang som affiliate
> Fra konto til første udbetaling – opret en konto, tilføj dine sites, find programmer, lav links og følg dine resultater.
Som affiliate anbefaler du butikker på dine sites, i dine videoer, opslag eller nyhedsbreve og tjener provision, når en kunde køber via dit link. Hele forløbet foregår i affiliate-dashboardet på **app.affilyflow.com**. Denne side tager dig igennem det i den rækkefølge, du møder det.
## 1. Opret en konto
1. Gå til [app.affilyflow.com/signup](https://app.affilyflow.com/signup).
2. Skriv dit navn, din e-mail og en adgangskode på mindst 8 tegn.
3. Åbn mailen fra Affilyflow, og tryk på linket for at bekræfte din e-mail. Linket udløber efter en time – bed om et nyt, hvis det er for gammelt.
Når du er logget ind, lander du på **Oversigt**. Menuen er delt i Overblik, Tjen penge, Kontakt og Konto.
> [!TIP]
> Vælg et visningsnavn under **Indstillinger**. Det er det navn, butikkerne ser. Uden et visningsnavn ser de kun dit AF-nummer – aldrig dit navn, din e-mail eller dine bankoplysninger.
## 2. Tilføj dine sites
Åbn **Links → Mine sites**, og tilføj de domæner, du promoverer på. Skriv kun domænet, fx `minside.dk` – uden `https://`, `www` og sti.
Du behøver ikke tagge dine links med et site. Affilyflow måler sitet på hvert klik ud fra den side, klikket kom fra. Et site, du tilføjer, viser derfor med det samme de klik og salg, der allerede er kommet derfra. Sites, der sender dig klik, men som du ikke har tilføjet, står som **Opdaget automatisk** med en knap til at tilføje dem.
Vil du vide præcis hvilken side på sitet, der sælger, så læs [Side-sporing](https://www.affilyflow.com/docs/affiliate/side-sporing).
## 3. Find programmer
Åbn **Programmer**. Alle programmer i kataloget er åbne – der er ingen ansøgning, og butikken skal ikke godkende dig. I stedet har hvert program regler, du skal overholde.
Programsiden viser det, du skal bruge for at vælge:
| Felt | Det betyder |
|---|---|
| Betaling | Din provision – i procent af salget eller et fast beløb pr. salg. Satsen er det, du får. |
| Cookie-varighed | Hvor længe efter klikket et køb tæller (butikken vælger mellem 14 og 90 dage). |
| Godkendelse af salg | Hvor mange dage der går, før et salg godkendes – 30, 45, 60 eller 90. |
| Abonnementer | Om du også får provision på fornyelser, og i hvor mange måneder. |
| Regler og kanaler | Tilladte kanaler, betalt søgning, rabatkode- og cashbacksider og rabatkoder. |
| Retningslinjer | Butikkens egne krav, skrevet af butikken. |
Under **Programmer → Produkter** kan du søge i butikkernes varer med pris, lager og en anslået provision pr. salg – og lave et link direkte til varen.
Et program vises først i kataloget, når butikken har sat sin hjemmeside op, og sporingen kører. Så ved du, at et salg via dit link kan registreres.
## 4. Tilmeld dig et program
Tryk **Tilmeld program** på programsiden. Du er med med det samme, og butikken får besked. Tilmeldingen giver dig dit første link til programmet.
Læs reglerne, før du går i gang – især tilladte kanaler, betalt søgning og rabatkodesider. Brud på reglerne kan koste provisionen. Se [Programregler](https://www.affilyflow.com/docs/affiliate/regler).
## 5. Lav dine links
Tryk **Opret link** (på Oversigt, under Dine links eller under Værktøjer). Vælg programmet og eventuelt en bestemt side hos butikken. Under de ekstra felter kan du sætte sub-ID, kanal og kampagne.
Når linket er klar, har du to knapper:
- **Kopier link** – til sociale medier, videoer, mails og andre steder, hvor du kun kan indsætte en adresse.
- **Kopier HTML** – til dit eget site. HTML-koden får browseren til at sende den præcise side med, så du kan se hvilken artikel der sælger.
Alt om deeplinks, sub-ID, kampagner og QR-koder står i [Links og sub-ID](https://www.affilyflow.com/docs/affiliate/links-og-sub-id).
> [!NOTE]
> En rabatkode giver ikke provision i sig selv – det er klikket på dit link, der krediterer dig. Del koden sammen med linket.
## 6. Følg dine resultater
Under **Resultater** finder du tre faner:
- **Overblik** – klik, salg og provision for perioden, fordelt på butikker, kilder, sub-ID, enheder og lande.
- **Klik & salg** – hvert klik med siden, linket stod på, kilden, landingssiden hos butikken og det salg, det eventuelt førte til.
- **Salg** – hvert salg med status, beløb, provision og hele klikkets spor.
**Links → Placeringer** viser dine sider med klik, salg og indtjening pr. side.
Sådan tælles klik og salg:
- Provisionen og cookie-vinduet låses i det øjeblik, der klikkes. En butik, der senere sænker satsen, rammer ikke klik fra før.
- Et besøg er et klik: klikker samme besøgende flere gange på dine links til samme program inden for 30 minutter, tæller det en gang.
- Klikker du selv på dine links, mens du er logget ind på app.affilyflow.com, tæller det ikke. Du kan se dem som **Dine testklik** under Klik & salg.
- Kendte bots og klik fra skyservere tæller ikke som klik. Kunden sendes altid videre til butikken.
Et nyt salg står som **Afventer**. Butikken kan afvise det med en årsag inden for programmets godkendelsesperiode – fx ved en returnering. Afviser butikken ikke, godkendes salget automatisk, når perioden er gået. Salgsdetaljen viser datoen.
## 7. Bliv udbetalt
Godkendt provision udbetales automatisk den første bankdag i måneden, når du har mindst 100 kr til rådighed – eller når du trykker **Udbetal nu**. Før første udbetaling skal du under **Udbetaling** udfylde bankoplysninger og stamdata og acceptere aftalen om selvfakturering.
Læs mere i [Udbetaling](https://www.affilyflow.com/docs/affiliate/udbetaling).
## Brug din AI-assistent
Du kan forbinde Claude, ChatGPT, Claude Code eller Cursor til din konto og lade assistenten finde programmer, tjekke reglerne, lave links og hente dine tal. Se [MCP-server](https://www.affilyflow.com/docs/affiliate/mcp). Bygger du selv, så brug [REST-API'et](https://www.affilyflow.com/docs/affiliate/rest-api).
Kilde: https://www.affilyflow.com/docs/start/kom-i-gang-affiliate
---
# Shopify
> Sporing med Affilyflow-appen og en Web Pixel – ingen kode og ingen ændringer i temaet.
På Shopify sker sporingen gennem Affilyflow-appen. Appen slår sporingen til med en Web Pixel, så der ikke skal røres ved temaet, og et temaskift ødelægger ingenting.
> [!WARNING]
> Appen er under review hos Shopify og ligger endnu ikke i App Store, så en Shopify-butik kan ikke installere den i dag. WooCommerce, Stripe og script på eget website virker nu. Skriv til info@affilyflow.com, hvis I sælger på Shopify.
## Sådan virker appen
- **Ingen kode.** Appen er hele installationen – ingen scripts i temaet og intet at taste ind.
- **Kontoen.** Installationen opretter selv jeres Affilyflow-konto. Har I allerede en konto, forbinder I den i appen med en sekscifret kode fra dashboardet.
- **Prisen.** I vælger provisionen i appen.
- **Testlinket.** I appen laver I et ægte sporingslink til jeres egen shop og gennemfører selv klik, køb og registreret salg. Testsalget koster intet og tilskrives en systemkonto, så jeres affiliate-tal ikke påvirkes.
- **Valuta.** Salg i enhver valuta med en dagskurs fra ECB registreres og omregnes til kroner med kursen på salgstidspunktet. Saldo, provision og udbetaling afregnes i dag i danske kroner.
## Har I allerede en konto
Forbind kontoen, før I vælger provision og laver testkøbet:
1. Åbn appen under **Apps → Affilyflow** i Shopify-admin.
2. Tryk **Har du allerede en Affilyflow-konto?**. Appen åbner en side hos os med en sekscifret kode.
3. Skriv koden i appen. Så bruger appen den konto, I er logget ind med.
Gør I det ikke, opretter appen en separat konto til Shopify-butikken, og salg og saldo havner to steder.
## Det skal I vide
- **Provisionens grundlag.** Provisionen regnes af varernes værdi efter rabat, uden fragt.
- **Refusioner.** Appen afviser ikke selv et refunderet køb. En refunderet ordre skal I selv afvise inden for godkendelsesperioden – se [Godkendelse og refusioner](https://www.affilyflow.com/docs/butik/godkendelse-og-refusioner).
- **Startkredit.** Konti, som appen opretter, får ikke startkreditten.
- **Synlighed.** Programmet bliver synligt for affiliates, når appen har meldt, at pixlen er aktiv.
Kilde: https://www.affilyflow.com/docs/butik/shopify
---
# WooCommerce
> Installer vores gratis plugin, indsæt forbind-koden – klik og salg spores fra jeres egen server.
På WooCommerce bruger I vores plugin, **Affilyflow – Affiliate Tracking**. Det husker affiliate-klikket og melder ordren fra jeres egen server, så den ikke tabes til en adblocker. I skal hverken røre temaet eller skrive kode.
## Før du starter
- WordPress med WooCommerce aktiveret, og en bruger, der må installere plugins.
- Jeres forbind-kode. Den står under **Program → Opsætning**, når I har valgt **WooCommerce**.
- Sitet skal være offentligt – et WordPress-site, der er sat til privat, kan hverken vi eller jeres kunder nå.
## Installation
1. Hent pluginnet på [store.affilyflow.com/downloads/affilyflow-woocommerce.zip](https://store.affilyflow.com/downloads/affilyflow-woocommerce.zip). Det er gratis.
2. I WordPress: gå til **Plugins → Tilføj nyt → Upload plugin**, vælg zip-filen, installer den, og aktiver pluginnet.
3. Åbn Affilyflow-siden. Hurtigste vej er linket **Affilyflow** ude til højre ved pluginnet på plugin-listen, lige ved siden af linket til at deaktivere det.
4. Indsæt forbind-koden i feltet, og tryk **Forbind**.
Så er I færdige. Salg fra jeres affiliates tælles automatisk.
> [!TIP]
> Kan I ikke finde **Upload plugin**, sidder knappen øverst på siden **Tilføj nyt**, til højre for overskriften. Kan I ikke se linket til Affilyflow-siden, ligger den også under **WooCommerce → Affilyflow** – eller under **Indstillinger → Affilyflow**, hvis WooCommerce ikke er aktiveret. Nogle hostede WordPress-opsætninger skjuler tredjeparts-menupunkter; så er linket på plugin-listen den eneste vej ind.
## Bekræft forbindelsen
Tryk **Kør selvtest** under **Opsætning**. Selvtesten beder pluginnet om at kalde os og viser, om det blev genkendt som jeres butik, og hvilken version I kører.
Selvtesten beviser, at pluginnet kan nå os. At ordrerne meldes, viser først et rigtigt salg – I behøver ikke lave et testkøb. Fejler selvtesten, står årsagerne i [Test og fejlfinding](https://www.affilyflow.com/docs/butik/test-og-fejlfinding).
## Hvad pluginnet gør
- **Klikket.** Når en kunde lander med `?afid=` fra et affiliate-link, gemmer pluginnet klik-id'et i en cookie fra jeres egen server i 90 dage. Programmets egen cookie-periode håndhæves på vores server.
- **Salget.** Ordren meldes, når den er betalt eller får status *processing* eller *completed* i WooCommerce.
- **Grundlaget.** Provisionen regnes af varernes værdi: ordrelinjerne efter rabat, uden moms og fragt. Betalings- og ekspeditionsgebyrer er ikke med; rabatter givet som negativt gebyr trækkes fra.
- **Varelinjerne** sendes med, så I og jeres affiliates kan se, hvilke varer der sælger.
- **Selvhenvisning.** Køber en affiliate gennem sit eget link, registreres salget som afvist. Det koster jer intet.
- **Hjerteslag.** Fra version 1.9.0 melder pluginnet sig en gang i døgnet, så vi kan skelne en stille butik fra en forbindelse, der er tabt.
- **Opdateringer.** Automatiske opdateringer er slået til, første gang pluginnet aktiveres. I kan slå dem fra på plugin-listen.
## Refusioner og annulleringer
En hel refusion, en annulleret eller en fejlet ordre afviser salget: ingen provision og intet gebyr, og affiliaten får besked.
Fra version 1.10.0 nedsætter en delvis refusion provision og gebyr i samme forhold:
- Refunderer I bestemte varer, nedsættes provisionen med præcis de varer, uden moms.
- En refusion af kun fragten ændrer ikke provisionen.
- En refusion af et beløb uden varer nedsætter provisionen med samme andel af ordren.
- Betaler programmet et fast beløb pr. salg, ændres det ikke af en delvis refusion.
Når refusionerne til sammen dækker hele ordren, afvises salget.
## Sidste klik vinder
Bruger I også andre affiliate-netværk, gælder det seneste affiliate-klik på tværs af netværkene (fra version 1.10.0, slået til som standard). Lander kunden efter et Affilyflow-klik via et andet netværks link, glemmer pluginnet vores klik, og ordren meldes ikke til os. Klikker kunden igen på et Affilyflow-link, er salget vores igen.
Pluginnet genkender Partner-Ads, Tradedoubler (også Grow), Adtraction, Awin, impact.com, CJ, Rakuten, Admitad og links mærket `utm_medium=affiliate`. Indstillingen hedder **Sidste klik vinder** på Affilyflow-siden i WordPress.
Pluginnet bestemmer kun over Affilyflow. Skal de andre netværks pixels følge samme regel, kan I betinge dem med vinderen, som står i cookien `afw_last_network` og i PHP-funktionen `affilyflow_last_network( $order )`:
```php title="functions.php · eksempel"
add_action( 'woocommerce_thankyou', function ( $order_id ) {
$network = function_exists( 'affilyflow_last_network' ) ? affilyflow_last_network( $order_id ) : '';
if ( in_array( $network, array( '', 'partner-ads' ), true ) ) {
// Partner-Ads' salgspixel her
}
if ( in_array( $network, array( '', 'tradedoubler' ), true ) ) {
// Tradedoublers konverteringstag her
}
}, 20 );
```
En tom værdi betyder, at pluginnet ikke kender et affiliate-klik, så netværkets egen cookie afgør det som før.
> [!WARNING]
> Pixels, som et andet plugin selv sætter ind (fx Grow by Tradedoubler, der melder fra tak-siden på egen hånd), styres ikke af indstillingen. Spørg det netværk, hvordan de betinges.
## Hvilke data pluginnet sender
Det anonyme klik-id, ordrenummer, beløb uden moms, valuta og varelinjer – og et envejs-fingeraftryk (SHA-256) af faktura-e-mailen, som kun sammenlignes med affiliatens egen e-mail for at fange selvhenvisning. Selve e-mailadressen sendes aldrig, og der sendes ingen navne, adresser eller betalingsdata.
## Lead-programmer
Pluginnet melder køb, ikke tilmeldinger. Betaler programmet pr. lead, skal I bruge scriptet i stedet:
1. Installer det gratis plugin WPCode.
2. Indsæt sporingskoden fra **Opsætning** i **Code Snippets → Header & Footer → Header**.
Vil I tælle leads uden mere kode, så skriv adressen på jeres tak-side under **Automatisk lead-sporing** i **Opsætning**, før I kopierer sporingskoden – koden får så tak-sidens adresse med (`data-lead-url`). Lader I feltet stå tomt, viser Opsætning et lead-script til tak-siden i stedet.
Kilde: https://www.affilyflow.com/docs/butik/woocommerce
---
# Smartweb og HostedShop
> Sporing på Smartweb, HostedShop og den nye Dandomain Webshop – et script i designet og en salgskode i affiliate-feltet.
Smartweb, HostedShop og den nye Dandomain Webshop er samme system med samme menuer og variabler, så en guide dækker dem alle. Opsætningen kræver to felter to forskellige steder: sporingskoden på alle sider og salgskoden i affiliate-feltet.
> [!NOTE]
> Den ældre DanDomain-motor (adresser med `/shop/frontpage.html` og `[[variabler]]`) er en helt anden platform. Den har sin egen guide: [DanDomain Classic](https://www.affilyflow.com/docs/butik/dandomain-classic).
## Før du starter
- Adgang til shoppens administration.
- Jeres butiks-id. Kodestumperne under **Program → Opsætning** har det sat ind; her står `DIN_BUTIK` i stedet.
- Opsætning genkender som regel platformen selv. Står den ikke valgt, så vælg **Smartweb / HostedShop**.
## 1. Sporingskoden på alle sider
Gå til **Webshop → Design → Handlinger → Indsæt Javascript**, og sæt koden i feltet **Hvis koden skal afvikles i starten, når sidens header indlæses**.
```html title="Sporingskode – skal på alle sider"
```
Koden hører til designet. Skifter I design senere, skal den sættes ind igen.
> [!TIP]
> Kan I ikke se **Handlinger**? Så kører shoppen det ældre DanDomain-system. Vælg **DanDomain Classic** i Opsætning, så får I den rigtige guide.
## 2. Salgskoden i affiliate-feltet
Gå til **Indstillinger → Affiliate marketing**, og sæt koden i feltet. Shoppen kører den selv på ordrebekræftelsen og udfylder ordrenummer, beløb og valuta, hvor der står %-variabler. I skal ikke rette i koden.
```html title="Salgskode – kun i affiliate-feltet"
```
> [!WARNING]
> Platformen har ingen beløbsvariabel uden moms og fragt. Koden bruger ordrens total uden gebyr divideret med 1,25 (dansk moms), så **fragten tæller med** i grundlaget for provisionen. Sælger shoppen med en anden momssats, passer divisionen ikke – skriv til info@affilyflow.com.
Kan I ikke finde **Affiliate marketing**, så søg efter "affiliate" i indstillingerne. Feltet hedder det samme på Smartweb, HostedShop og Dandomain Webshop.
## 3. Test
Tryk **Åbn testlink** under testtrinnet i Opsætning, læg en testordre i samme browser, og bliv på siden. Live-testen viser testklikket og købet med ordrenummer og beløb, og spørger om beløbet passer. Et køb uden klik fra et link tælles ikke.
Kommer salget ikke frem, så tjek, at koden står i begge felter – og ikke den samme kode to steder. Se også [Test og fejlfinding](https://www.affilyflow.com/docs/butik/test-og-fejlfinding).
## Typiske fejl
| Det ser I | Årsag |
|---|---|
| Opsætning finder ikke sporingskoden | Koden står ikke i det design, der er **aktivt**, eller i det forkerte af de tre felter. |
| Konsollen skriver "salgskoden skal i Indstillinger -> Affiliate marketing" | Salgskoden ligger et sted uden ordre, fx i designets JavaScript-felt. Flyt den til affiliate-feltet. |
| Konsollen skriver "sporingskoden (track.js) mangler paa siden" | Sporingskoden er ikke kommet med på ordrebekræftelsen. Tjek trin 1. |
## Refusioner
Platformen melder ikke refusioner til os. Et refunderet køb skal I selv afvise inden for godkendelsesperioden – se [Godkendelse og refusioner](https://www.affilyflow.com/docs/butik/godkendelse-og-refusioner).
Kilde: https://www.affilyflow.com/docs/butik/smartweb
---
# DanDomain Classic
> Sporing på den ældre DanDomain-motor – sporingskoden i sitets kodefelt og salgskoden på Bestil step 4.
DanDomain Classic er den ældre DanDomain-motor. I kender den på adresser med `/shop/frontpage.html` og på `[[variabler]]` i skabelonerne. Den har to kodefelter i hver sin del af administrationen: et, der skrives ud på alle sider, og et, der kun vises efter et gennemført køb.
> [!NOTE]
> Kører shoppen den nye Dandomain Webshop (samme motor som Smartweb og HostedShop), så brug [Smartweb og HostedShop](https://www.affilyflow.com/docs/butik/smartweb) i stedet.
## Før du starter
- Adgang til shoppens administration.
- Jeres butiks-id. Kodestumperne under **Program → Opsætning** har det sat ind; her står `DIN_BUTIK` i stedet.
- Opsætning genkender som regel platformen selv. Står den ikke valgt, så vælg **DanDomain Classic**.
## 1. Sporingskoden i sitets kodefelt
Gå til **Opsætning → Site → rediger jeres site → Kodefelt**, og sæt koden ind. Feltet skrives ud i toppen af alle sider.
```html title="Sporingskode – skal på alle sider"
```
- Hedder menupunktet **Indstillinger** i stedet for **Opsætning**, er det samme sted – DanDomain kalder det begge dele.
- Har I flere sites i samme shop, skal koden ind på hvert site.
## 2. Salgskoden på Bestil step 4
Gå til **Design → Tekster/knapper → Bestil step 4 → Kodefelt**, og sæt salgskoden ind. Siden vises kun efter et gennemført køb.
```html title="Salgskode – kun på Bestil step 4"
```
Shoppen udfylder selv ordrenummer og beløb, hvor der står `[[variabler]]`. Beløbet er kurvens total uden moms, fragt og gebyr – præcis det, provisionen skal regnes af.
> [!WARNING]
> Der findes også **Design → Skins → Bestil step 4**. Det er designskabelonen og det forkerte sted. Salgskoden skal i **Tekster/knapper**.
**Valuta:** DanDomain har ingen variabel med valutakoden. Står beløbet med en kode som `EUR 49,95`, bruges den; står der "kr.", bruges butikkens egen valuta.
## 3. Test
Tryk **Åbn testlink** under testtrinnet i Opsætning, læg en testordre i samme browser, og bliv på siden. Live-testen viser testklikket og købet med ordrenummer og beløb, og spørger om beløbet passer. Et køb uden klik fra et link tælles ikke.
> [!TIP]
> Har I et Google Analytics-id i shoppens sporingsfelt, så tjek, at den samme kode ikke står to steder. DanDomain advarer selv mod dobbelt implementering, og det gælder også vores kode.
## Typiske fejl
| Det ser I | Årsag |
|---|---|
| Opsætning finder ikke sporingskoden | Har shoppen flere sites, skal koden i kodefeltet på det site, forsiden hører til. |
| Konsollen skriver "salgskoden skal i feltet paa Bestil step 4" | Salgskoden ligger i sitets kodefelt. Flyt den til Tekster/knapper → Bestil step 4. |
| Salget kommer uden ordrenummer | Variablen `[[OrderID]]` blev ikke udfyldt. Uden nummer kan en genindlæst kvittering tælle som et nyt salg. |
Mere i [Test og fejlfinding](https://www.affilyflow.com/docs/butik/test-og-fejlfinding).
## Refusioner
Platformen melder ikke refusioner til os. Et refunderet køb skal I selv afvise inden for godkendelsesperioden – se [Godkendelse og refusioner](https://www.affilyflow.com/docs/butik/godkendelse-og-refusioner).
Kilde: https://www.affilyflow.com/docs/butik/dandomain-classic
---
# Shoporama
> Sporingskoden i et HTML-felt og salgskoden i temaets thanks.html – med beløbet uden moms og fragt.
På Shoporama ligger sporingskoden i et HTML-felt, som kommer på alle sider. Salgskoden skal derimod i temaets **thanks.html** – kvitteringssiden – fordi det kun er der, Shoporama kender ordren.
## Før du starter
- En bruger med adgang til **Webshop → HTML-felter**.
- Adgang til temaets filer til salgskoden. Har I den ikke selv, så send guiden til jeres udvikler med **Send til udvikler** i Opsætning, eller spørg Shoporamas support (support@shoporama.dk), om de vil sætte koden ind.
- Jeres butiks-id. Kodestumperne under **Program → Opsætning** har det sat ind; her står `DIN_BUTIK` i stedet.
## 1. Sporingskoden i et HTML-felt
Gå til **Webshop → HTML-felter**, og opret et nyt felt:
1. Giv det et navn uden mellemrum, fx `affilyflow`.
2. Vælg placeringen **i slutningen af ``**.
3. Sæt koden ind, og gem.
```html title="Sporingskode – i et HTML-felt"
```
Shoporama sætter feltet på alle sider – også kurv, kasse og kvittering.
> [!NOTE]
> Shoporama kan vise en side fra sin cache i op til 4 timer. Vil I se koden med det samme, så åbn shoppen i den browser, hvor I er logget ind i admin, eller sæt `?test=1` efter adressen.
## 2. Salgskoden i thanks.html
Sæt koden i temaets **thanks.html**. Den kan stå hvor som helst i filen og rører ikke Shoporamas egen sporing.
```smarty title="Salgskode – kun i temaets thanks.html"
<{if $order}>
<{/if}>
```
Shoporama udfylder selv ordrenummer, valuta og beløb, hvor der står `<{…}>`. Beløbet er ordrens total uden moms minus fragten uden moms – rabatter er allerede trukket fra. Shoporama dokumenterer ikke, om et eventuelt betalingsgebyr indgår i totalen.
> [!WARNING]
> Salgskoden må ikke i et HTML-felt. Der kører Shoporama ikke de variabler, der giver ordrenummer og beløb, så koden sender intet. Står `<{if $order}>` som tekst på siden, eller skriver browserens konsol "salgskoden skal i temaets thanks.html", så flyt koden til thanks.html.
## 3. Test
Tryk **Åbn testlink** under testtrinnet i Opsætning, læg en testordre i samme browser, og bliv på siden. Live-testen viser testklikket og købet med ordrenummer og beløb, og spørger om beløbet passer. Et køb uden klik fra et link tælles ikke.
Kommer salget ikke frem, så tjek, at sporingskoden ligger i et HTML-felt og salgskoden i thanks.html – ikke omvendt, og ikke den samme kode to steder.
## Typiske fejl
| Det ser I | Årsag |
|---|---|
| Opsætning finder ikke sporingskoden | Siden kommer måske fra Shoporamas cache – giv det et øjeblik. Tjek også, at feltet indsættes **i slutningen af ``**: et felt, der ikke tilføjes automatisk, kommer ikke på siderne. |
| Kan ikke se **HTML-felter** under Webshop | Brugeren mangler adgang. Bed butikkens ejer om at give den eller sætte koden ind. |
| Konsollen skriver "fragten er stoerre end ordrens total" | Tallene fra temaet passer ikke sammen. Skriv til info@affilyflow.com. |
## Cookiebanner
Bruger shoppen Shoporamas eget cookiebanner, kan sporingskoden vente på kundens samtykke til marketing. Se [Samtykke og cookies](https://www.affilyflow.com/docs/butik/samtykke-og-cookies).
## Refusioner
Shoporama melder ikke refusioner til os. Et refunderet køb skal I selv afvise inden for godkendelsesperioden – se [Godkendelse og refusioner](https://www.affilyflow.com/docs/butik/godkendelse-og-refusioner).
Kilde: https://www.affilyflow.com/docs/butik/shoporama
---
# Webflow
> Sporingskoden i sitets Custom code og en linje på Order Confirmation – uden skabelonvariabler.
Webflow har ingen skabelonvariabler, men det behøver salget heller ikke: linjen på ordrebekræftelsen henter ordrenummeret fra sidens adresse og beløbet fra et felt på siden.
I Opsætning ligger Webflow under **Andet**. Vælg **Andet**, og tryk **Vis Webflow-guiden**.
## Før du starter
- Custom code kræver et betalt Webflow site plan.
- Adgang til sitet i Webflow Designer.
- Jeres butiks-id. Kodestumperne under **Program → Opsætning** har det sat ind; her står `DIN_BUTIK` i stedet.
## 1. Sporingskoden på hele sitet
Åbn **Site settings → Custom code**, indsæt scriptet i feltet **Head code**, og tryk **Save changes**.
```html title="Site settings → Custom code → Head code"
```
## 2. Salget på ordrebekræftelsen
1. Åbn siden **Order Confirmation** i Designer.
2. Marker teksten med ordrens **subtotal**, og giv den id'et `af-total` under **Element settings → ID**.
3. Indsæt linjen under **Page settings → Custom code → Before `` tag**.
```html title="Ordrebekræftelse → Before "
```
Ordrenummeret hentes fra parameteren `orderId` i sidens adresse, og beløbet fra elementet med id'et `af-total`, når siden har vist det. Sporingskoden venter op til 20 sekunder på, at begge dele er der.
- `data-affilyflow-vat="25"` trækker 25 % moms fra. Fjern den, hvis jeres priser er uden moms.
- Fragten indgår ikke i subtotalen.
- Sælger I i en anden valuta end kroner, så tilføj fx `data-affilyflow-currency="EUR"`.
Alle attributter står i [Salgslinjen](https://www.affilyflow.com/docs/reference/salgslinje).
## 3. Publicer og test
Tryk **Publish** – custom code er først live, når sitet er publiceret. Tryk derefter **Åbn testlink** under testtrinnet i Opsætning, læg en testordre i samme browser, og bliv på siden. Live-testen viser testklikket og købet med ordrenummer og beløb, og spørger om beløbet passer.
| Det ser I | Årsag |
|---|---|
| Konsollen skriver "data-affilyflow-order must be the order number" | Adressen på ordrebekræftelsen har ingen `orderId`. |
| Konsollen skriver "data-affilyflow-total must be the amount" | Intet element har id'et `af-total`, eller det viste intet beløb inden for 20 sekunder. |
Mere i [Test og fejlfinding](https://www.affilyflow.com/docs/butik/test-og-fejlfinding).
## Lead-programmer
Betaler programmet pr. tilmelding, skal salgslinjen ikke bruges. Skriv adressen på jeres tak-side under **Automatisk lead-sporing** i Opsætning, og kopier derefter sporingskoden igen – den får tak-sidens adresse med (`data-lead-url`) og tæller så leads alene. Ellers viser Opsætning et lead-script, som kun må ligge på tak-siden (**Page settings → Custom code → Before `` tag**) – ikke i sitets fælles footer, for så tælles leads på alle sider.
Kilde: https://www.affilyflow.com/docs/butik/webflow
---
# Specialbygget webshop
> Sæt sporingen op på en webshop, der er kodet til jer, fx i Laravel eller PHP – uden at kende feltnavne.
Har I en webshop, der er udviklet til jer – fx i Laravel, Symfony eller et andet framework – kræver sporingen to ting: et script på alle sider og en måde at melde salget på. Salget kan I ofte melde uden at røre kvitteringssiden, fordi jeres site allerede sender købet til Google Analytics. Ellers er det en enkelt linje HTML.
## Før du starter
- Jeres hjemmeside skal være gemt i installationstrinnet under **Program → Opsætning** – scanningen og testlinket bruger den.
- Vælg **Specialbygget webshop** under **Hvor sælger du?**.
- Jeres butiks-id. Kodestumperne i Opsætning har det sat ind; her står `DIN_BUTIK` i stedet.
## Det beløb, provisionen regnes af
Provisionen regnes af **varerne efter rabat, uden moms og fragt**. Gebyrer er heller ikke med. Det er det beløb, salget skal meldes med – uanset hvilken vej I vælger nedenfor.
## 1. Scriptet på alle sider
Indsæt scriptet i `` i jeres fælles layout, så det er med på alle sider – også kvitteringen. Det husker affiliate-klikket, når kunden lander, så et senere køb kan krediteres.
```html title=" · alle sider"
```
Scriptet har `async`, så det aldrig holder siden tilbage. Hvad det gemmer, og hvornår, står i [track.js](https://www.affilyflow.com/docs/reference/track-js).
## 2. Start med "Scan jeres site"
Tryk **Scan jeres site** i salgstrinnet. Vi henter jeres forside og en produktside (højst tre sider, evt. via en kategoriside) og kigger efter to ting:
- **Google Analytics 4-e-handel** – hændelser som `view_item`, `add_to_cart` eller `begin_checkout` med varer, sendt gennem `dataLayer` eller `gtag`.
- **Google Tag Manager eller et GA4-tag** uden synlige købsdata.
Finder vi e-handelsdata, måler vi også, om beløbene i Google Analytics er med moms: vi sammenligner prisen i `view_item` med den pris, produktsiden selv viser. Kan det ikke måles, bruges jeres lands standardmoms.
Scanningen ser også efter et cookiebanner. Bruger sitet Cookiebot eller Cookie Information, sættes **Cookiebanner** til *Vent på samtykke*: scriptet får `data-consent="required"` og gemmer først klikket, når kunden har sagt ja til marketing. Indstillingen kan altid skiftes i Opsætning. Se [Samtykke og cookies](https://www.affilyflow.com/docs/butik/samtykke-og-cookies).
| Resultat | Det betyder |
|---|---|
| Fundet: jeres site sender købsdata til Google Analytics | Opsætning skifter til **Automatisk fra Google Analytics**. Se vej A. |
| Google Tag Manager fundet – men ingen købsdata at se | Ved jeres udvikler, at Tag Manager sender købet til GA4, kan I vælge vej A. Ellers vej B. |
| Ingen købsdata fra Google Analytics fundet | Brug linjen på kvitteringssiden – vej B. |
| Sitet afviste scanningen | Fx et bot-værn. Brug vej B, eller vælg vej A, hvis I ved, at sitet sender købet til GA4. |
Under **Sådan meldes salget** kan I altid skifte mellem **Automatisk fra Google Analytics** og **Linje på kvitteringssiden**.
> [!WARNING]
> Brug en vej, ikke begge. Har kvitteringssiden allerede salgslinjen, skal scriptet ikke også have `data-sale="ga4"`.
## Vej A: Automatisk fra Google Analytics
Sender jeres kvitteringsside allerede en GA4-hændelse `purchase`, kan scriptet læse købet selv. Der skal ingen kode på kvitteringssiden, og ingen behøver kende skabelonens feltnavne. Scriptet får blot `data-sale="ga4"`:
```html title=" · alle sider · med Google Analytics"
```
Kopier den præcise linje fra Opsætning – den har jeres indstillinger med:
- `data-sale-vat="25"` står der, når beløbene i Google Analytics er med 25 % moms. Momsen trækkes fra. Er beløbene uden moms, står attributten der ikke.
- `data-sale-shipping="incl"` eller `"excl"` står der, når I har valgt **Med fragt** eller **Uden fragt**. Med **Find selv** gætter scriptet: er `value` større end varelinjerne, regnes fragten med og trækkes fra.
Scriptet læser hændelsen i begge former:
```js title="Det læser scriptet på kvitteringssiden"
dataLayer.push({ event: 'purchase', ecommerce: { transaction_id: 'A-1008', value: 724.00, shipping: 49.00, currency: 'DKK', items: [ /* ... */ ] } });
// eller
gtag('event', 'purchase', { transaction_id: 'A-1008', value: 724.00, shipping: 49.00, currency: 'DKK', items: [ /* ... */ ] });
```
Ordrenummeret er `transaction_id`, og beløbet er `value` – minus fragten, når den er med – divideret med momsen. Scriptet holder øje med `dataLayer` i 60 sekunder, så en hændelse, der kommer efter siden er indlæst, kommer også med.
> [!TIP]
> Moms og fragt gemmes hos os, når I vælger dem i Opsætning, og vi regner GA4-beløbet om med jeres indstilling, når salget kommer ind. Retter I momsen eller fragten senere, gælder det fra næste salg – scriptet på sitet skal ikke ændres.
Mere om vejen i [Google Analytics](https://www.affilyflow.com/docs/reference/google-analytics).
## Vej B: Linjen på kvitteringssiden
Sender sitet ikke købet til Google Analytics, sætter I en linje HTML et sted i `` på kvitteringssiden – siden kunden ser efter et gennemført køb:
```html title="Kvitteringssiden · et sted i "
```
Erstat `DIN_ORDRE_ID` med jeres variabel for ordrens nummer og `DIT_BELOEB` med beløbet for varerne efter rabat, uden moms og fragt. Der er ingen JavaScript at skrive: scriptet finder linjen og melder salget.
Sådan ser det ud i de mest almindelige skabelonsprog. **Feltnavnene er eksempler** – brug dem, jeres system har på kvitteringssiden:
```blade title="Laravel (Blade)"
```
```php title="PHP"
```
```twig title="Twig"
```
```liquid title="Liquid"
```
```smarty title="Smarty"
```
> [!WARNING]
> En "subtotal" er ikke altid uden moms. Er jeres beløb med moms, så brug et beløb uden – eller tilføj `data-affilyflow-vat="25"`, så trækkes 25 % fra. Ordrenummeret skal være det rigtige nummer: uden det kan en genindlæst kvittering tælle som et nyt salg.
### Det kan linjen også
| Attribut | Virkning |
|---|---|
| `data-affilyflow-vat="25"` | Beløbet er med 25 % moms, som trækkes fra. |
| `data-affilyflow-currency="EUR"` | Valutaen, når I ikke sælger i kroner. Standard er `DKK`. |
| `data-affilyflow-order="?orderId"` | Læser ordrenummeret fra parameteren `orderId` i sidens adresse. |
| `data-affilyflow-total="#af-total"` | Læser beløbet fra teksten i elementet med id'et `af-total`. |
Beløb som `1249.50` og `1.249,50` virker begge. Tegner jeres side kvitteringen efter indlæsning (fx Livewire eller React), finder scriptet linjen alligevel: det holder øje med siden i 60 sekunder, og når linjen er der, venter det op til 20 sekunder på, at ordrenummer og beløb er udfyldt. Hver linje meldes kun en gang.
Alle detaljer står i [Salgslinjen](https://www.affilyflow.com/docs/reference/salgslinje).
## Kender I ikke feltnavnene?
Det gør jeres kode. Under **Lad jeres AI-kodeassistent sætte det op** i Opsætning ligger en færdig prompt. Kopier den ind i Claude Code, Cursor, Copilot eller ChatGPT i jeres projekt – assistenten kan se koden, finde kvitteringssiden og de rigtige felter og indsætte linjen. Prompten ser sådan ud (med jeres adresse og butiks-id):
```text title="Prompt til AI-kodeassistenten"
Sæt Affilyflow-affiliatesporing op i dette projekt (dinbutik.dk).
1. Indsæt dette script i i det fælles layout, så det er med på alle sider:
2. Find visningen, kunden ser efter et gennemført køb (ordrebekræftelsen/kvitteringen), og de variabler, der er tilgængelige der. Indsæt dette et sted i på den side:
- ORDRENUMMER = ordrens nummer, som kunden ser det.
- BELØB = varernes beløb efter rabat, UDEN moms og UDEN fragt og gebyrer, med punktum som decimaltegn (fx 540.00). Findes kun et beløb med moms, så divider med 1.25 (25 % moms).
- Brug skabelonsprogets almindelige udskrivning (med escaping). Opfind ikke feltnavne – find dem i ordremodellen, controlleren eller visningen.
- Ændr ikke checkout eller andre filer end layoutet og kvitteringsvisningen.
3. Skriv til sidst, hvilke filer du ændrede, og hvilke variabler du brugte til ordrenummer og beløb.
Testen sker bagefter i Affilyflow: Opsætning → Åbn testlink → læg en testordre. Siden viser ordrenummer og beløb, der kom frem.
```
Ved I selv, hvad felterne hedder, så åbn **Ved I selv, hvad felterne hedder?** og skriv variablerne for ordrenummer og beløb – linjen bygges med dem. Knapperne under felterne viser skrivemåden i Blade, PHP, Twig, Liquid og Smarty.
Er det en udvikler eller et bureau, der skal gøre det, så tryk **Send til udvikler**. Vi mailer guiden med scriptet, linjen, prompten og testtrinnet.
## 3. Test
1. Tryk **Åbn testlink** under testtrinnet, og læg en testordre i den fane, der åbner – i samme browser.
2. Bliv på Opsætning. Live-testen viser testklikket og derefter købet med ordrenummer og beløb. Den følger med i op til 10 minutter.
3. Svar på spørgsmålet: **Er beløbet varernes beløb efter rabat, uden moms og fragt, på din testordre?** Svarer I nej, skriver I det rigtige beløb, og live-testen siger, hvad der er galt – fx at beløbet er med moms eller med fragt.
På vej A viser live-testen også udregningen, fx "724,00 kr. fra Google Analytics − 49,00 kr. fragt ÷ 1,25 = 540,00 kr." Er beløbet med moms eller fragt, kan I rette det med et klik (**Træk momsen fra** eller **Træk fragten fra**). Rettelsen gælder fra næste salg, uden at scriptet skal ændres – lav et nyt testkøb for at se det.
Et testkøb giver ingen provision eller gebyr og tæller ikke med i jeres tal. Annuller ordren bagefter i jeres shop.
> [!NOTE]
> Programmet bliver først synligt for affiliates, når scriptet er fundet på forsiden, og salgskoden har meldt et køb. Testkøbet tæller.
## Typiske fejl
Fejl fra linjen eller scriptet står i browserens konsol på kvitteringssiden:
| Konsollen skriver | Årsag |
|---|---|
| `data-affilyflow-order must be the order number` | Ordrenummeret er tomt eller står stadig som pladsholder. |
| `data-affilyflow-total must be the amount (goods excl. VAT and shipping)` | Beløbet er tomt eller 0. |
| `the purchase event has no amount` | GA4-hændelsen `purchase` havde intet beløb. |
Kommer der slet intet salg, så tjek, at scriptet også ligger på kvitteringssiden, og at testkøbet er lavet i samme browser som testlinket. Mere i [Test og fejlfinding](https://www.affilyflow.com/docs/butik/test-og-fejlfinding).
## Lead-programmer
Betaler programmet pr. lead i stedet for pr. køb, skal der ikke meldes et beløb. Skriv adressen på jeres tak-side under **Automatisk lead-sporing** i Opsætning, så får scriptet tak-sidens adresse med og tæller leads alene:
```html title=" · alle sider · lead-program"
```
Her er scriptet uden `async`, fordi det skal nå at tælle leadet, før tak-siden eventuelt sender kunden videre. Lader I feltet stå tomt, viser Opsætning i stedet et lead-script til tak-siden.
## Andre måder at melde salget
Linjen og Google Analytics-vejen dækker de fleste systemer. Den ældre JavaScript-funktion `affilyflow.conversion()` virker stadig – se [track.js](https://www.affilyflow.com/docs/reference/track-js). Melder I salget fra jeres egen server, så brug [konverterings-API'et](https://www.affilyflow.com/docs/reference/konverterings-api).
Kilde: https://www.affilyflow.com/docs/butik/specialbygget
---
# Stripe
> Spor salg fra Stripe Checkout og Payment Links med en webhook – engangskøb, abonnementer og refusioner.
Tager kunderne betaling i Stripe Checkout eller via Payment Links på jeres eget site, melder Stripe salget direkte til os gennem en webhook. Det dækker engangskøb, abonnementer og fornyelser. Der skal ikke et sporingsscript på siden – men klik-id'et skal med til Stripe.
> [!NOTE]
> Kører shoppen på Shopify, WooCommerce eller en af de danske platforme, så vælg den platform i Opsætning – også selvom betalingen går gennem Stripe. Vælg kun **Stripe**, når kunderne betaler i Stripe Checkout eller via Payment Links.
## Før du starter
- Adgang til jeres Stripe-dashboard.
- Adgang til koden, der opretter Checkout Sessions – eller til jeres Payment Links.
- Jeres butiks-id. Adressen under **Program → Opsætning** har det sat ind; her står `DIN_BUTIK` i stedet.
Vi beder aldrig om jeres Stripe-API-nøgle. I giver os kun webhookens signing secret, som udelukkende kan bekræfte, at en besked kommer fra jer.
## 1. Opret webhooken
I Stripe: gå til **Workbench → Webhooks → Create an event destination**.
1. Vælg **Your account** og payload-formatet **Snapshot** – ikke Thin, som Stripe foreslår. Thin sender ikke ordredataene, og så kan salget ikke registreres.
2. Vælg begivenhederne:
- `checkout.session.completed`
- `checkout.session.async_payment_succeeded`
- `invoice.payment_succeeded`
- `customer.subscription.deleted`
- `charge.refunded`
- `charge.dispute.created`
3. Vælg **Webhook endpoint**, og brug adressen:
```text title="Webhook-URL"
https://app.affilyflow.com/api/stripe/webhook/DIN_BUTIK
```
`invoice.payment_succeeded` og `customer.subscription.deleted` skal kun med, når I sælger abonnementer med provision på fornyelser.
> [!WARNING]
> Refusions-begivenhederne er vigtige: uden dem bliver et refunderet køb eller en indsigelse til provision, I skal betale. Og `checkout.session.async_payment_succeeded` fanger betalinger, der først gennemføres senere (fx bankoverførsel) – uden den registreres de salg aldrig.
## 2. Indsæt signing secret
Kopier endpointets **Signing secret** (starter med `whsec_`), og indsæt den under **Opsætning** i Affilyflow. Uden den afviser vi alle hændelser. Send den ikke i en mail – det er butiksejeren, der indsætter den.
## 3. Send klik-id'et med
Affiliates sender kunder til jeres site med `?afid=…` i adressen. Gem klik-id'et i en cookie på jeres topdomæne, når kunden lander, så det overlever skiftet fra www til fx et app-subdomæne:
```js title="Alle sider"
// On every page: keep the click ID from ?afid= in a cookie on your main domain (90 days)
const afid = new URLSearchParams(location.search).get('afid');
if (afid && /^c_[A-Za-z0-9_-]{4,64}$/.test(afid)) {
const domain = location.hostname.split('.').slice(-2).join('.');
document.cookie = 'af_click=' + afid + '; domain=.' + domain + '; path=/; max-age=7776000; SameSite=Lax; Secure';
}
// On your server, read the af_click cookie from the request when creating the Checkout Session.
```
Send det med som `client_reference_id`, når I opretter Checkout Session:
```js title="client_reference_id"
// stripe.checkout.sessions.create({ ...,
client_reference_id: req.cookies.af_click || undefined,
// })
```
- Domænet regnes som de to sidste led af adressen. Har jeres domæne flere led (fx `.co.uk`), så ret det til jeres eget.
- Opretter I sessionen på serveren, skal værdien sendes med fra browseren i forespørgslen.
- Bruger I **Payment Links**, så sæt klik-id'et på linket som `?client_reference_id=…`.
Tjek bagefter i Stripe, at `client_reference_id` på begivenheden `checkout.session.completed` er `"c_…"` og ikke `null`. Er den `null`, nåede klik-id'et ikke frem – tjek cookie-domænet.
## Hvad Stripe melder
- **Beløbet.** Provisionen regnes af betalingens beløb uden moms, direkte fra Stripe.
- **Valuta.** Salg i andre valutaer omregnes til kroner med ECB-kursen på salgstidspunktet, og kursen låses på salget.
- **Abonnementer.** Slå recurring provision til under **Program**, så giver hver fornyelse automatisk provision, så længe programmet siger. Samme faktura kan aldrig tælle to gange.
- **Refusioner.** En hel refusion eller en indsigelse afviser salget, og affiliaten får besked. En delvis refusion nedsætter provision og gebyr i samme forhold. Betaler programmet et fast beløb pr. salg, ændres det ikke af en delvis refusion.
## Test
Stripe sender ikke testbetalinger videre til os – betalinger i testtilstand registreres ikke. Vil I teste:
1. Tryk **Åbn testlink** under testtrinnet i Opsætning.
2. Køb noget billigt med et rigtigt kort i samme browser.
3. Refunder købet bagefter i Stripe.
Live-testen i Opsætning viser testklikket og købet. Ellers bekræftes sporingen af sig selv ved det første affiliate-køb, og programmet bliver synligt for affiliates, når vi har modtaget den første hændelse fra jeres webhook.
Kilde: https://www.affilyflow.com/docs/butik/stripe
---
# Test og fejlfinding
> Bevis, at sporingen virker, med et testkøb – og find årsagen, når et salg ikke kommer frem.
Kliktal alene beviser ingenting: klik registreres på vores server, også helt uden kode hos jer. Det er salget, der skal testes. Den hurtigste vej er live-testen under **Program → Opsætning**.
## Live-testen
1. Tryk **Åbn testlink** under testtrinnet. Testlinket er et rigtigt affiliate-link til jeres shop, som åbner i en ny fane.
2. Læg en ordre i den fane – i samme browser.
3. Bliv på Opsætning. Siden tjekker hvert fjerde sekund i op til 10 minutter og viser:
- **Testklik registreret** med tidspunktet,
- **Testkøb registreret** med ordrenummer og beløb,
- en advarsel, hvis købet blev afvist, eller ordrenummeret manglede.
4. Svar på, om beløbet er varernes beløb efter rabat, uden moms og fragt. Svarer I nej og skriver det rigtige beløb, siger live-testen, om beløbet er med moms, med fragt, for lavt eller noget andet.
Et testkøb giver ingen provision eller gebyr og tæller ikke med i jeres tal. Annuller ordren bagefter i jeres shop.
Shopify-butikker bruger testlinket i Affilyflow-appen. Stripe sender ikke testbetalinger videre – se [Stripe](https://www.affilyflow.com/docs/butik/stripe). WooCommerce bekræftes med selvtesten nedenfor.
## Test forbindelsen
**Test forbindelse** (eller **Tjek igen**) henter jeres forside og tjekker, at sporingskoden ligger der. Det beviser scriptet – ikke salget.
| Svar | Gør sådan |
|---|---|
| Vi mangler din webshops adresse | Gem hjemmesiden i installationstrinnet. |
| Vi kunne ikke nå siden | Tjek, at siden er live, og at adressen er rigtig. |
| Vi fandt ikke sporingskoden | Følg installationstrinnet igen, gem ændringerne, og test igen. Se også platformens guide. |
| Websitet afviser vores automatiske tjek | Ikke en fejl i opsætningen (fx et bot-værn). Bekræft i stedet med et testkøb. |
På Shoporama kan forsiden komme fra cachen i op til 4 timer – giv det et øjeblik.
Når scriptet er fundet, tjekker vi forsiden igen hver 6. time. Forsvinder sporingskoden, fx efter en temaopdatering, sender vi en mail, når den går fra at virke til at være nede.
## Når live-testen viser en fejl
| Live-testen siger | Årsag og løsning |
|---|---|
| Et køb kom uden klik-id | Købet blev lavet i en anden browser end testlinket. Køb i samme browser – helst i den fane, testlinket åbnede. |
| Købet kom med et klik-id, vi ikke kender | Åbn testlinket igen, og køb i samme browser. |
| Klikket var for gammelt | Klikket er ældre end programmets cookie-periode. Åbn testlinket igen, og køb lige efter. |
| Beløbet kom ikke med (0 kr.) | Feltet med beløbet er tomt eller forkert. |
| Ordrenummeret kom ikke med | Uden det kan en genindlæst kvittering tælle som et nyt salg. Tjek feltet med ordrens nummer. |
| Lead-koden ligger på siden, men programmet betaler pr. salg | Fjern lead-koden, og brug salgskoden. |
| Valutaen kunne ikke omregnes | Skriv til info@affilyflow.com. |
| Vi har ikke set noget i 10 minutter | Tjek, at koden ligger, som guiden viser, og tryk **Start forfra**. |
## Statuskoderne
Hvert salg, vi modtager, får en status – også dem, der ikke bliver til et salg. Dem, en butik typisk møder:
| Status | Det betyder |
|---|---|
| `ok` | Salget er registreret og tilskrevet en affiliate (eller jeres testlink). |
| `duplicate` | Samme ordre kom igen og blev ikke talt to gange. Normalt, fx ved en genindlæst kvittering. |
| `unknown` | Klik-id'et findes ikke – forkert, slettet eller aldrig registreret. |
| `expired` | Klikket var ældre end programmets cookie-periode. |
| `invalid_value` | Beløbet var negativt eller ulæseligt. Tyder på en fejl i salgskoden. |
| `zero_value` | Ordren var på 0 kr. |
| `store_mismatch` | Klikket hører til en anden butik end salget. Tjek, at `data-store` er jeres eget butiks-id. |
| `value_too_high` | Ordren var over programmets beløbsloft (standard 50.000 kr.) og blev ikke registreret. |
| `unsupported_currency` | Valutaen kunne ikke omregnes. |
| `program_inactive` | Programmet var slået fra, da salget kom. |
| `stale_lead_trigger` | En lead-kode på et program, der betaler pr. salg. Fjern den gamle lead-kode. |
| `blocked` | Affiliaten er blokeret, så salget gav ingen provision. |
| `self_referral` | Køberen har samme e-mail som affiliaten. Registreret som afvist – det koster jer intet. |
| `error` | Registreringen fejlede. Skriv til info@affilyflow.com. |
Hele listen står i [Statuskoder](https://www.affilyflow.com/docs/reference/statuskoder).
## Fejl i browserens konsol
Salgskoderne skriver i konsollen (udviklerværktøjerne), når de ikke kan melde et salg. Åbn konsollen på kvitteringssiden efter et testkøb:
| Konsollen skriver | Årsag |
|---|---|
| `Affilyflow: sporingskoden (track.js) mangler paa siden` | Scriptet er ikke på kvitteringssiden. Platformenes salgskoder (Smartweb, DanDomain, Shoporama) venter 10 sekunder på det og skriver så denne fejl. |
| `data-affilyflow-order must be the order number` | Salgslinjens ordrenummer er tomt eller en pladsholder. |
| `data-affilyflow-total must be the amount` | Salgslinjens beløb er tomt eller 0. |
| `the purchase event has no amount` | Google Analytics-hændelsen `purchase` havde intet beløb. |
| `salgskoden skal i …` | Salgskoden ligger på et forkert sted, hvor ordren ikke findes. Teksten siger hvor. |
## WooCommerce: selvtesten
Tryk **Kør selvtest** i Opsætning. Selvtesten beder pluginnet om at kalde os og viser plugin-versionen. Den beviser, at pluginnet kan nå os – at ordrerne meldes, viser først et rigtigt salg.
| Selvtesten siger | Gør sådan |
|---|---|
| Pluginnet er installeret, men ikke forbundet | Indsæt forbind-koden på Affilyflow-siden i WordPress, og tryk **Forbind**. |
| Forbind-koden blev ikke genkendt | Indsæt jeres nuværende kode igen. |
| Pluginnets forbindelse er forældet | Opdater pluginnet under Plugins, og indsæt forbind-koden igen. |
| Ruten findes ikke | Opdater pluginnet til 1.6 eller nyere, og tjek, at det er aktiveret. |
| Sitet er sat til privat | Gå til **Indstillinger → Læsning** i wp-admin, og gør sitet offentligt. Så længe det er privat, kan en kunde heller ikke købe gennem et affiliate-link. |
| WordPress afviste kaldet | Tjek, at sitet er offentligt, og at intet sikkerheds-plugin blokerer REST-API'et. |
| Pluginnet kunne ikke nå Affilyflow | Jeres host blokerer udgående kald – det rammer også rigtige salg. Bed hosten om at tillade udgående HTTPS til Affilyflow. |
| Webshop-URL'en omdirigerer | Ret adressen under **Indstillinger**, og test igen. |
Fra version 1.9.0 melder pluginnet sig en gang i døgnet, og pluginnets egen side i WordPress viser, hvornår det sidst nåede os.
## Kommer salget stadig ikke frem?
- Ligger scriptet på **alle** sider – også landingssiden og kvitteringen?
- Står salgskoden det rigtige sted, og kun et sted?
- Er testkøbet lavet i samme browser som testlinket?
- Venter scriptet på samtykke (`data-consent="required"`)? Så skal I sige ja til marketing-cookies i testen – se [Samtykke og cookies](https://www.affilyflow.com/docs/butik/samtykke-og-cookies).
Sidder I fast, så opret en supportsag i dashboardet eller skriv til info@affilyflow.com.
Kilde: https://www.affilyflow.com/docs/butik/test-og-fejlfinding
---
# Samtykke og cookies
> Hvad sporingen gemmer i kundens browser, hvor længe – og hvordan scriptet kan vente på samtykke til marketing.
Sporingen gemmer kun et klik-id: det id, der kobler et senere køb til det klik, der skabte det. Det ligger i en førstepartscookie på jeres domæne – ikke på vores – og der laves ingen sporing på tværs af sites eller enheder.
## Hvad der gemmes
Sporingsscriptet (track.js) gemmer:
| Navn | Hvor | Indhold | Levetid |
|---|---|---|---|
| `af_click` | Cookie og localStorage | Klik-id'et fra affiliate-linket | 90 dage fra klikket |
| `af_store` | Cookie og localStorage | Jeres butiks-id | 90 dage fra klikket |
| `af_aff` | Cookie og localStorage | Affiliatens id – kun ved ældre links | 90 dage fra klikket |
| `af_sent` | localStorage | Op til 20 ordrenumre, der allerede er meldt | 30 dage |
| `af_sent_s`, `af_lead_[butik]` | sessionStorage | Værn mod at tælle samme køb eller lead to gange | Til fanen lukkes |
| `_af_d` | Cookie | Test af, hvilket domæne cookien kan sættes på | Slettes med det samme |
Scriptet skriver cookien igen ved hvert besøg, men aldrig ud over 90 dage fra klikket. Programmets egen cookie-periode – mellem 14 og 90 dage – håndhæves på vores server ud fra klikkets tidspunkt.
WooCommerce-pluginnet sætter `af_click` fra jeres egen server (90 dage). Med **Sidste klik vinder** slået til sætter det også `afw_last_network` (90 dage), som kun indeholder navnet på det netværk, der havde kundens seneste affiliate-klik.
Cookierne hører hjemme i jeres egen cookie-oplysning. Vores [cookiepolitik](/cookiepolitik) beskriver dem, så I kan henvise til den.
## Cookie-domænet
Cookien lægges på det bredeste domæne, browseren tillader – `dinbutik.dk` frem for `www.dinbutik.dk` – så klikket overlever, hvis kvitteringen ligger på et andet subdomæne. Vil I bestemme domænet selv, så tilføj `data-cookie-domain`:
```html title=" · alle sider"
```
## Vent på samtykke
Med `data-consent="required"` gemmer scriptet intet i browseren – ingen cookie, intet i localStorage eller sessionStorage og ingen testcookie – før den besøgende har givet samtykke til marketing:
```html title=" · alle sider · med samtykke"
```
Attributten er valgfri. På en [specialbygget webshop](https://www.affilyflow.com/docs/butik/specialbygget) tilføjer Opsætning den selv, når scanningen finder Cookiebot eller Cookie Information (indstillingen **Cookiebanner**). På de andre platforme sætter I den selv. Uden attributten opfører scriptet sig som altid.
Indtil der er samtykke, ligger klik-id'et kun i hukommelsen, og et salg venter på samtykkets svar. Uden samtykke tilskrives intet – heller ikke på samme side.
### Sådan læser scriptet samtykket
Scriptet læser selv disse cookie-løsninger:
- **Cookiebot** – samtykke til marketing.
- **Cookie Information** – kategorien `cookie_cat_marketing`.
- **IAB TCF v2** – publisher-samtykke til formål 1 og 7.
- **Shoporamas eget cookiebanner** – samtykke til marketing.
Bruger I en anden løsning, så giv scriptet besked selv:
```js title="Jeres egen cookie-løsning"
// Den besøgende siger ja til marketing:
affilyflow.consent(true);
// Den besøgende siger nej eller trækker samtykket tilbage:
affilyflow.consent(false);
```
I kan også sætte `window.affilyflowConsent = true`, før scriptet indlæses.
Et nej sletter alt, scriptet har gemt (`af_click`, `af_store`, `af_aff` og `af_sent`). Et nej registreres kun ved en aktiv handling fra den besøgende – aldrig fordi banneret endnu ikke har læst sin egen cookie.
> [!WARNING]
> Med `data-consent="required"` skal I selv sige ja til marketing, når I laver testkøbet – ellers kommer det ikke frem i live-testen.
## Shopify og WooCommerce
Shopify-pixlen og WooCommerce-pluginnet spørger ikke selv om samtykke. Det hører til butikkens egen cookie-løsning.
## Safari og iPhone
Safari sletter cookies og localStorage, som er sat af JavaScript, efter cirka 7 dage uden besøg på siden – i nogle tilfælde efter 24 timer. Scriptet skriver begge dele igen ved hvert besøg, men kan og skal ikke omgå grænsen. Derfor tæller hele cookie-perioden på computer og Android, mens den i Safari og på iPhone typisk er cirka 7 dage. En cookie sat af butikkens egen server, som WooCommerce-pluginnets, lever længere.
Kilde: https://www.affilyflow.com/docs/butik/samtykke-og-cookies
---
# Godkendelse og refusioner
> Godkendelsesperioden, hvordan I afviser et salg, og hvornår en refusion rettes af sig selv.
Et nyt salg står som **Afventer**. I godkendelsesperioden kan I afvise det med en årsag; når perioden er slut, godkendes det automatisk. Først et godkendt salg trækker provision og gebyr fra jeres saldo.
## Godkendelsesperioden
Perioden sættes under **Program** og kan være 30, 45, 60 eller 90 dage. Sæt den til jeres returret – så bliver provision aldrig udbetalt for en vare, der sendes retur. En længere periode betyder, at affiliates venter længere på pengene.
- Perioden låses på salget, når det registreres. Ændrer I den senere, gælder den nye periode kun for nye salg.
- Under **Salg** står, hvor mange dage der er tilbage til auto-godkendelse.
- Når perioden er slut, kan salget ikke længere afvises.
## Afvis eller godkend et salg
Åbn salget under **Resultater → Salg**:
- **Afvis** kræver en årsag. Affiliaten får besked om afvisningen og årsagen. Et afvist salg koster jer intet.
- **Godkend** kan bruges før tid, hvis salget er i orden. En godkendelse kan ikke fortrydes – provisionen bliver udbetalt til affiliaten.
Testsalg fra jeres testlink skal hverken godkendes eller afvises. De koster intet og tæller ikke med.
## Salg, der ikke godkendes automatisk
Nogle salg markeres **Kræver vurdering** og godkendes ikke automatisk, når perioden er slut. Affiliaten kan ikke se markeringen. I godkender salget, hvis det er rigtigt, eller afviser det med en årsag. Det sker, når:
- klikket kom mindre end en time efter, at affiliatens konto blev oprettet, eller
- det er affiliatens første salg, og kontoen er under 14 dage gammel.
Står der **Affilyflow vurderer**, gennemgår vi salget efter programmets regler og afgør det selv. I skal ikke gøre noget, og I betaler kun, hvis det godkendes.
Køber en affiliate gennem sit eget link med samme e-mail, registreres salget som afvist med det samme og koster jer intet. Det fanges, når salget kommer fra WooCommerce-pluginnet eller Stripe.
## Refusioner, der rettes af sig selv
| Integration | Hel refusion eller annullering | Delvis refusion |
|---|---|---|
| WooCommerce-plugin | Salget afvises. Også en annulleret eller fejlet ordre. | Fra version 1.10.0 nedsættes provision og gebyr i samme forhold. |
| Stripe | Salget afvises – også ved en indsigelse (`charge.dispute.created`). | Provision og gebyr nedsættes i samme forhold. |
En automatisk afvisning rammer også et salg, der allerede er godkendt, og affiliaten får besked. Er salget allerede faktureret, krediteres det på en kreditnota.
Ved en delvis refusion fra WooCommerce gælder:
- Refunderer I bestemte varer, nedsættes provisionen med præcis de varer, uden moms.
- En refusion af kun fragten ændrer ikke provisionen.
- Når refusionerne til sammen dækker hele ordren, afvises salget.
Betaler programmet et **fast beløb pr. salg**, ændres provisionen ikke af en delvis refusion – kun en hel refusion afviser salget. Leads berøres ikke af refusioner.
## Refusioner, I selv skal afvise
Shopify-appen, script på eget website, Smartweb, HostedShop, DanDomain, Shoporama og Webflow melder ikke refusioner til os. Et refunderet køb skal I selv afvise under **Salg** inden for godkendelsesperioden – det sker ikke af sig selv.
> [!TIP]
> Bruger I en af disse platforme, så sæt godkendelsesperioden mindst lige så lang som jeres returret. Når perioden er slut, kan salget ikke længere afvises.
Kilde: https://www.affilyflow.com/docs/butik/godkendelse-og-refusioner
---
# Produkter og feed
> Sådan kommer jeres varer i affiliates' produktsøgning – fra jeres shop eller et Google Shopping- eller CSV-feed.
Affiliates kan søge i jeres varer med pris, lager og et skøn over, hvad de tjener – og linke direkte til varen. Det kræver ingen installation: vi henter varerne hver nat. Under **Program → Produkter** ser I kataloget, som affiliates ser det, og bestemmer, hvad der vises.
## Hvor varerne kommer fra
Vi henter automatisk fra jeres shops offentlige katalog, når det er tilgængeligt:
- **Shopify** – det offentlige produktkatalog.
- **WooCommerce** – Store API'et.
Deler shoppen ikke et offentligt katalog, eller vil I styre det selv, så brug et **produktfeed**. Et feed vinder altid over kataloget.
| Kilde | Varer pr. butik |
|---|---|
| Offentligt katalog | Op til 1.000 |
| Produktfeed | Op til 5.000 |
## Brug et produktfeed
Indsæt feedets adresse under **Kilde og regler** på **Produkter**, og tryk **Hent og gem feed**. Vi henter feedet, før det gemmes – kan det ikke læses, gemmes intet.
Vi læser:
- **Google Shopping** som XML/RSS eller Atom,
- **CSV** med Googles eller Metas kolonnenavne.
Varianter samles på `item_group_id`, og en udsalgspris med datoer bruges i perioden. Feedet må være op til 20 MB og må gerne være gzip-komprimeret. Linjer uden id, titel eller link springes over.
```text title="Feed-URL · eksempel"
https://dinbutik.dk/feed/google.xml
```
Tøm feltet for at gå tilbage til det offentlige katalog.
| Fejlen siger | Gør sådan |
|---|---|
| Adressen er en almindelig webside, ikke et feed | Brug feedets egen adresse – den ender typisk på `.xml` eller `.csv`. |
| Feedet er større end 20 MB | Del det op, eller brug et feed uden beskrivelser. |
| Feedet er tomt | Vi fandt ingen varer med id, titel og link. |
| Din shop deler ikke et offentligt katalog | Indsæt en feed-URL, fx jeres Google Shopping-feed. |
## Hentningen
Vi henter varerne automatisk hver nat. Har I ændret priser eller lager, kan I også hente dem med det samme fra **Produkter**. Siden viser kilden, seneste hentning, seneste vellykkede hentning, antal varer og en fejl i klart sprog, hvis hentningen fejler.
Ændrer I hjemmesiden under **Indstillinger**, henter vi kataloget igen.
## Bestem, hvad affiliates ser
- **Skjul en vare** med kontakten **Synlig** i kataloget.
- **Skjul udsolgte varer** samlet. De kommer frem igen, så snart de er på lager.
Skjulte varer forsvinder fra søgningen, men links, der allerede er lavet, virker stadig.
## Moms og "Affiliaten tjener"
Affiliates får provision af prisen uden moms. Derfor er "Affiliaten tjener" et skøn: programmets sats af prisen minus momsen. Det endelige beløb regnes af ordren, når salget kommer ind.
Under **Priserne i dit katalog er** vælger I **Automatisk**, **Inkl. moms** eller **Uden moms**. Automatisk bruger momsen i det land, shoppen oplyser – ellers faktureringslandet eller domænet. Kender vi ikke landet, bruger vi forsigtigt 25 %.
## Tal pr. vare
Kataloget viser for hver vare klik, salg og omsætning i den valgte periode:
- **Klik** tælles, når en affiliate linker direkte til varens side.
- **Salg** kobles på via varelinjerne i ordren. Dem sender WooCommerce-pluginnet og Shopify-appen. Salgslinjen på kvitteringssiden og Google Analytics-vejen sender kun ordrenummer og beløb, så salg pr. vare vises ikke for dem.
Under **Hvad affiliates søger efter** ser I søgninger fra de seneste 90 dage, der rammer jeres varer eller var filtreret på jeres butik – kun antal, aldrig hvem.
## Hvornår varerne vises
Varerne vises i affiliates' produktsøgning, når programmet er synligt: hjemmesiden er gemt, og sporingen kører. Se [Kom i gang som butik](https://www.affilyflow.com/docs/start/kom-i-gang-butik).
Kilde: https://www.affilyflow.com/docs/butik/produkter-og-feed
---
# track.js
> Komplet reference til sporingsscriptet – attributter, JavaScript-API, hvad der gemmes i browseren, samtykke og fejl.
track.js er Affilyflows sporingsscript til webshops og websites. Det gemmer klikket, når en kunde lander fra et affiliate-link, og melder salget, når kunden når kvitteringen. Hvordan det hele hænger sammen, står i [Sådan virker sporingen](https://www.affilyflow.com/docs/start/sadan-virker-sporingen).
## Indsæt scriptet
Indsæt linjen i `` på alle sider – også kvitteringssiden.
```html title=" · alle sider"
```
`DIN_BUTIK` er butikkens id. Den rigtige linje med jeres id står under Opsætning i butikkens dashboard – kopier den derfra.
Brug `async`: scriptet må aldrig holde siden tilbage. Undtagelsen er den kodefri lead-trigger (`data-lead-url`), hvor linjen skal være uden `async` – se [Lead uden kode](#lead-uden-kode).
## Attributter
| Attribut | Påkrævet | Betydning |
|---|---|---|
| `data-store` | Ja | Butikkens id. Sendes med hvert salg og skal passe til klikkets butik, ellers afvises salget som `store_mismatch`. |
| `data-consent="required"` | Nej | Intet gemmes i browseren, før den besøgende har givet samtykke til marketing. Se [Samtykke](#samtykke). |
| `data-lead-url` | Nej | Adressen på tak-siden for et lead-program. Når siden matcher, meldes et lead automatisk. Se [Lead uden kode](#lead-uden-kode). |
| `data-cookie-domain` | Nej | Domænet, cookien lægges på (fx `shop.dk`). Uden attributten vælger scriptet selv det bredeste domæne, browseren tillader. |
| `data-sale="ga4"` | Nej | Læs købet fra Google Analytics 4 (dataLayer eller gtag). Se [Google Analytics](https://www.affilyflow.com/docs/reference/google-analytics). |
| `data-sale-vat` | Nej | Kun med `data-sale="ga4"`: momsen i procent, som Google Analytics-beløbene indeholder (fx `25`). |
| `data-sale-shipping` | Nej | Kun med `data-sale="ga4"`: `incl` hvis fragten altid er med i `value`, `excl` hvis den aldrig er. Uden attributten gætter scriptet. |
## Hvad scriptet gemmer
Når en kunde lander med `?afid=` i adressen, gemmer scriptet klik-id'et både som cookie og i localStorage, så det overlever, at den ene af dem ryddes.
| Nøgle | Indhold |
|---|---|
| `af_click` | Klik-id'et fra `?afid=` |
| `af_store` | Butikkens id fra `data-store` |
| `af_aff` | Kun ved ældre direkte links (`?aff=` + `?store=`) |
- **Levetid:** højst 90 dage fra klikket. Cookiens `max-age` er altid den resterende tid af de 90 dage.
- **Genbesøg forlænger aldrig vinduet.** Ved hvert besøg skrives cookie og localStorage igen med den resterende tid, så en slettet cookie genskabes fra localStorage – men tidspunktet er altid klikkets.
- **localStorage** gemmer værdien som `{ v, t }` – værdien og tidspunktet for klikket. En værdi over 90 dage gammel slettes ved læsning.
- **Cookien** sættes med `path=/` og `SameSite=Lax`, og med `Secure` på https.
- **Domænet:** cookien lægges på det bredeste domæne, browseren tillader (`shop.dk` frem for `www.shop.dk`), så klikket følger med til en kvittering på et andet subdomæne. Scriptet finder det med en kortvarig testcookie (`_af_d`), der slettes med det samme. På en IP-adresse eller `localhost` bruges den præcise host.
- **Hukommelsen:** klik-id'et fra adressen ligger også i sidens hukommelse, så et salg på samme side virker, selvom lageret er blokeret (fx et privat vindue).
> [!NOTE]
> Serveren afgør det rigtige vindue. Hvert program har sit eget cookie-vindue, som låses på klikket. Er klikket ældre end programmets vindue, afvises salget som `expired` – også selvom browseren stadig har det.
### Kun Affilyflows klik-id'er
Kun et `afid`, der ligner vores (`c_` efterfulgt af 4–64 tegn: bogstaver, tal, `_` og `-`), gemmes. Bruger butikken også et andet netværk med en parameter, der hedder `afid`, kan den fremmede værdi derfor aldrig overskrive et Affilyflow-klik.
## affilyflow.conversion()
Meld et salg fra JavaScript. Kaldet virker kun, når track.js er indlæst på siden.
```js title="Kvitteringssiden"
affilyflow.conversion({ order: '1008', total: 499, currency: 'DKK' });
```
| Felt | Type | Betydning |
|---|---|---|
| `order` | tekst | Ordrenummeret. Bruges til at stoppe dubletter. |
| `total` | tal eller tekst | Beløbet, provisionen regnes af: varerne efter rabat, uden moms og fragt. Tekst som `"1.249,00"` læses, se [Beløb som tekst](#beloeb-som-tekst). |
| `currency` | tekst | Valutakode, fx `DKK` eller `EUR`. Gøres til store bogstaver. Standard er `DKK`. |
| `store` | tekst | Butikkens id. Bruges kun, hvis browseren ikke allerede har det fra `data-store`. |
Sådan opfører kaldet sig:
- **Uden et affiliateklik i browseren sendes intet** – og der skrives ingen fejl. Et almindeligt køb uden affiliate er ikke en fejl.
- Salget sendes med `navigator.sendBeacon`, så det kommer frem, selvom siden skifter med det samme bagefter. Uden sendBeacon bruges en usynlig billed-anmodning.
- Med `data-consent="required"` venter kaldet, til samtykket er afklaret, og sendes først derefter.
- Til et lead-program: `affilyflow.conversion({ order: 'lead_' + Date.now() })` – intet beløb.
> [!WARNING]
> Med `async` er `window.affilyflow` ikke sikkert klar, når en inline-kode på kvitteringen kører. Vent på den, eller brug [salgslinjen](https://www.affilyflow.com/docs/reference/salgslinje), som slet ikke kræver JavaScript.
```html title="Kvitteringssiden · vent på track.js"
```
### Beløb som tekst
Et tal bruges, som det er. Tekst, der er et gyldigt maskintal, bruges også, som den er. Anden tekst renses: alt andet end cifre, punktum, komma og minus fjernes, og den **sidste** separator er decimaltegnet. Står der kun en separator med præcis tre cifre efter, er den tusindtal.
| Sendt som `total` | Læses som |
|---|---|
| `499` | 499 |
| `"1249.5"` | 1249,5 |
| `"249.125"` | 249,125 |
| `"1.249,00"` | 1249 |
| `"1.249,-"` | 1249 |
| `"499,00 kr."` | 499 |
| `"kr. 499"` | 499 |
| `"1,234.56"` | 1234,56 |
> [!WARNING]
> Teksten `"1.249"` er et gyldigt maskintal og læses af `conversion()` som 1,249. Send hele kroner som tal (`1249`) eller med decimaler (`"1.249,00"`). [Salgslinjen](https://www.affilyflow.com/docs/reference/salgslinje) læser derimod `"1.249"` som 1249, fordi værdien kommer fra sidens tekst.
### Pladsholdere, der afvises
Står skabelonens pladsholder stadig i `order`, sendes salget **uden** ordrenummer – en pladsholder må aldrig optage et ordrenummer, så alle senere salg blev afvist som dubletter. Disse værdier regnes som pladsholdere (store og små bogstaver er ligegyldige):
- `DIN_ORDRE_ID`, `DIT_ORDRE_ID`, `ORDRE_ID`, `ORDER_ID`, `YOUR_ORDER_ID`, `ORDERID`
- `XX`, `XXX` osv. (to eller flere X)
- Alt i `{{ … }}`, `[ … ]`, `< … >` eller `% … %` – en skabelonvariabel, der ikke blev udfyldt
Serveren bruger samme regel.
### Dubletværn i browseren
Serveren tæller aldrig samme ordrenummer to gange i samme program. Browseren husker desuden, hvad den har sendt, så en genindlæst kvittering ikke sender igen:
- **Med ordrenummer:** gemt i localStorage (`af_sent`) i 30 dage.
- **Uden ordrenummer:** gemt i sessionStorage (`af_sent_s`) som side og beløb – samme kvittering med samme beløb sendes en gang pr. fane.
## affilyflow.consent()
Butikkens egen samtykkeløsning kan styre scriptet direkte. Det er kun relevant med `data-consent="required"`.
```js title="Efter den besøgendes valg"
affilyflow.consent(true); // gem klikket fra adressen, og send ventende salg
affilyflow.consent(false); // slet af_click, af_store, af_aff og af_sent
```
Siger den besøgende ja igen på samme side, gemmes klikket fra adressen igen.
## Samtykke
Med `data-consent="required"` gemmer scriptet intet i browseren – ingen cookie, intet localStorage eller sessionStorage, ingen testcookie – før der er givet samtykke til marketing. Indtil da ligger klikket kun i sidens hukommelse og bruges ikke. Uden attributten opfører scriptet sig som før.
```html title=" · alle sider"
```
Scriptet læser selv samtykket fra:
| Kilde | Hvad der tæller som ja |
|---|---|
| `affilyflow.consent(true)` | Kaldet |
| `window.affilyflowConsent` | Værdien `true` |
| Shoporamas banner | `shoporamaConsent.isAllowed('marketing')` |
| Cookie Information | Kategorien `cookie_cat_marketing` |
| Cookiebot | `Cookiebot.consent.marketing` |
| IAB TCF v2 | Publisher-samtykke til formål 1 og 7 (eller GDPR gælder ikke) |
- Et ja virker, uanset om banneret svarer før eller efter scriptet. Cookie Informations, Cookiebots og Shoporamas hændelser lyttes på hele tiden. `window.affilyflowConsent` tjekkes ved indlæsning og de første 10 sekunder, og TCF bruges, når dens API findes inden for de 10 sekunder.
- Et nej sletter kun ved en aktiv handling (Cookie Informations og Cookiebots hændelser, TCF's `useractioncomplete` eller `affilyflow.consent(false)`) – aldrig fordi banneret endnu ikke har læst sin egen cookie.
- Sætter I `window.affilyflowConsent` senere end 10 sekunder efter indlæsning, så kald `affilyflow.consent(true)` i stedet.
Se [Samtykke og cookies](https://www.affilyflow.com/docs/butik/samtykke-og-cookies) for opsætningen.
## Lead uden kode
Til lead-programmer kan scriptet selv melde et lead, når kunden når tak-siden. Sæt adressen i `data-lead-url`, og fjern `async`, så leadet sendes, før tak-siden eventuelt viderestiller.
```html title=" · alle sider · lead-program"
```
- `/tak` rammer `/tak` og `/tak/…`, men ikke `/takeaway`.
- En fuld adresse (`https://shop.dk/tak`) læses som stien.
- Står der parametre (`/tak?type=lead`), skal de alle findes i sidens adresse.
- Forsiden alene (`/`) matcher kun, når der også står parametre.
- Leadet meldes en gang pr. session og kun med et affiliateklik i browseren.
> [!NOTE]
> Et lead (`order` der begynder med `lead_`) på et program, der betaler pr. salg, afvises som `stale_lead_trigger`. Fjern lead-triggeren, når programmet er skiftet til salg.
## Fejl i konsollen
track.js skriver kun i browserens konsol, når et salg ikke kunne meldes fra [salgslinjen](https://www.affilyflow.com/docs/reference/salgslinje) eller [Google Analytics](https://www.affilyflow.com/docs/reference/google-analytics):
| Besked | Årsag |
|---|---|
| `Affilyflow: data-affilyflow-order must be the order number – the sale was not recorded.` | Salgslinjens ordrenummer mangler eller er en pladsholder. |
| `Affilyflow: data-affilyflow-total must be the amount (goods excl. VAT and shipping) – the sale was not recorded.` | Salgslinjens beløb er tomt eller 0. |
| `Affilyflow: the purchase event has no amount – the sale was not recorded.` | Google Analytics-købet havde intet beløb. |
Se [Test og fejlfinding](https://www.affilyflow.com/docs/butik/test-og-fejlfinding) for at teste hele vejen med testlinket.
## Safari og iPhone
Safari (ITP) sletter cookies og localStorage, der er sat fra JavaScript, efter cirka 7 dage uden besøg på siden. track.js genskriver lageret ved hvert besøg, men kan og skal ikke omgå grænserne. Kun en cookie sat af butikkens egen server – fx WooCommerce-pluginnet – lever længere. Det gælder alle netværk, ikke kun Affilyflow.
## Ældre direkte links
Links med `?aff=` (eller `?aff_id=`) og `?store=` i stedet for `?afid=` virker stadig. Scriptet tæller klikket og gemmer `af_aff` og `af_store`. `data-store` vinder altid over `?store=` i adressen, så en butik, der selv bruger `?store=`, ikke får sine salg afvist.
Kilde: https://www.affilyflow.com/docs/reference/track-js
---
# Salgslinjen
> Meld salget med en enkelt linje HTML på kvitteringssiden – ingen JavaScript, og danske beløb læses rigtigt.
Salgslinjen er den enkleste måde at melde et salg på: et skjult element på kvitteringssiden, som [track.js](https://www.affilyflow.com/docs/reference/track-js) finder og melder. Der er ingen JavaScript at skrive, så en skabelonvariabel kan ikke ødelægge koden.
## Linjen
```html title="Kvitteringssiden"
```
Sæt jeres skabelonvariabler ind i stedet for `1008` og `499,00` – ordrenummeret og varernes beløb efter rabat, uden moms og fragt. Hvilke variabler det er på jeres system, står i [Specialbygget webshop](https://www.affilyflow.com/docs/butik/specialbygget).
Linjen virker kun, når track.js ligger på samme side. Indsæt scriptet på alle sider, også kvitteringen.
## Attributter
| Attribut | Påkrævet | Værdi |
|---|---|---|
| `data-affilyflow-order` | Ja | Ordrenummeret – eller `?parameter` for at læse det fra sidens adresse. |
| `data-affilyflow-total` | Ja | Beløbet – eller `#id` for at læse teksten i elementet med det id. |
| `data-affilyflow-vat` | Nej | Momsen i procent, som beløbet indeholder (fx `25`). Den trækkes fra. |
| `data-affilyflow-currency` | Nej | Valutakode, fx `EUR`. Standard er `DKK`. |
Brug `hidden`, så elementet ikke vises. Det betyder intet for sporingen.
## Værdier fra siden
Har sidebyggeren ingen skabelonvariabler (fx Webflow), kan linjen hente værdierne fra siden selv:
- `data-affilyflow-order="?orderId"` læser parameteren `orderId` i adressen (`/order-confirmation?orderId=WF-55` giver `WF-55`).
- `data-affilyflow-total="#af-total"` læser teksten i elementet med `id="af-total"`.
- `data-affilyflow-vat="25"` trækker 25 % moms fra, når beløbet på siden er med moms.
```html title="Kvitteringssiden · Webflow"
```
Med `1.249,00 kr.` i `#af-total` meldes 999,20 (1.249 uden 25 % moms).
## Sådan læses beløbet
- Danske beløb læses: `499,00`, `1.249,00`, `1.249,-`, `499,00 kr.` og `kr. 499`.
- `1.249` læses som 1249 – i linjen er punktum med tre cifre efter altid tusindtal, fordi værdien kommer fra sidens tekst.
- Maskintal som `1249.5` og `89.90` bruges, som de er.
- Med `data-affilyflow-vat` divideres beløbet med 1 + moms/100 og rundes til to decimaler. Værdien skal være over 0 og under 100.
Beløbet skal være det, provisionen regnes af: varerne efter rabat, uden moms og fragt.
## Timing
Mange sider tegner kvitteringen efter indlæsning (Livewire, React, Webflow). Linjen klarer det:
- track.js leder efter elementet, når siden er indlæst, og holder øje med nye elementer i op til 60 sekunder.
- Mangler ordrenummeret, eller har beløbet endnu ingen cifre, venter scriptet op til 20 sekunder på dem.
- Hvert element meldes en gang. Det markeres med `data-affilyflow-sent="1"`, når det er læst.
- Når et salg er fundet, holder scriptet op med at lede.
En genindlæst kvittering sendes ikke igen: browseren husker ordrenummeret i 30 dage, og serveren tæller aldrig samme ordrenummer to gange i samme program.
## Hvornår intet sendes
- **Intet affiliateklik i browseren.** Et almindeligt køb uden affiliate sendes ikke, og der skrives ingen fejl. Test derfor med testlinket – se [Test og fejlfinding](https://www.affilyflow.com/docs/butik/test-og-fejlfinding).
- **Intet samtykke endnu.** Med `data-consent="required"` venter salget, til samtykket er afklaret. Se [Samtykke og cookies](https://www.affilyflow.com/docs/butik/samtykke-og-cookies).
- **Ordrenummeret mangler eller er en pladsholder.** Linjen kræver et ordrenummer. `ORDRE_ID`, `{{ $order->id }}`, `[OrderID]`, `<…>` og `%OrderID%`, der ikke blev udfyldt, afvises.
- **Beløbet er tomt eller 0.**
De to sidste skriver en fejl i browserens konsol:
```text title="Browserens konsol"
Affilyflow: data-affilyflow-order must be the order number – the sale was not recorded.
Affilyflow: data-affilyflow-total must be the amount (goods excl. VAT and shipping) – the sale was not recorded.
```
> [!TIP]
> Live-testen under Opsætning i butikkens dashboard viser det beløb, der kom frem, og spørger, om det passer med testordren. Det fanger både moms og fragt, der er kommet med.
## Linjen eller JavaScript?
Linjen og [`affilyflow.conversion()`](https://www.affilyflow.com/docs/reference/track-js#affilyflow-conversion) melder det samme salg. Brug linjen, medmindre I allerede har et script, der virker – det gamle kald virker uændret. Brug kun en af dem på samme kvittering, eller send samme ordrenummer i begge, så dubletværnet stopper den anden.
Kilde: https://www.affilyflow.com/docs/reference/salgslinje
---
# Google Analytics
> Lad track.js læse købet fra Google Analytics 4 med data-sale="ga4" – ingen kode på kvitteringssiden.
Sender jeres site allerede købet til Google Analytics 4 – via Google Tag Manager (`dataLayer`) eller `gtag` – kan [track.js](https://www.affilyflow.com/docs/reference/track-js) læse ordrenummer og beløb derfra. Så skal ingen kende skabelonens feltnavne, og der er intet at indsætte på kvitteringen.
## Slå det til
Tilføj `data-sale="ga4"` til scriptet på alle sider. Den rigtige linje med jeres indstillinger står under Opsætning i butikkens dashboard.
```html title=" · alle sider"
```
| Attribut | Værdi |
|---|---|
| `data-sale="ga4"` | Læs købet fra Google Analytics 4. Uden den ignoreres Google Analytics helt. |
| `data-sale-vat` | Momsen i procent, som Google Analytics-beløbene indeholder. `25` hvis beløbene er med dansk moms; udelad den, hvis de er uden moms. |
| `data-sale-shipping` | `incl` hvis fragten altid er med i `value`, `excl` hvis den aldrig er. Uden attributten gætter scriptet. |
> [!WARNING]
> Brug kun en vej til salget. Har sitet WooCommerce-pluginnet eller [salgslinjen](https://www.affilyflow.com/docs/reference/salgslinje) på kvitteringen, så slå ikke `data-sale="ga4"` til – ellers kan samme køb blive meldt to gange med hvert sit ordrenummer. Derfor er Google Analytics-vejen kun aktiv med attributten.
## Formater, der læses
Købshændelsen `purchase` læses i alle tre former:
```js title="Google Tag Manager · dataLayer"
dataLayer.push({
event: 'purchase',
ecommerce: {
transaction_id: '1008',
value: 675,
shipping: 49,
currency: 'DKK',
items: [{ item_id: '317', price: 375, quantity: 2 }]
}
});
```
```js title="gtag"
gtag('event', 'purchase', { transaction_id: '1008', value: 1000, currency: 'DKK' });
```
En `purchase`-hændelse i `dataLayer` uden `ecommerce`-objekt læses også – så tages felterne direkte fra hændelsen.
- Hændelser, der allerede ligger i `dataLayer`, når scriptet indlæses, læses straks.
- Hændelser, der kommer bagefter, fanges med det samme – også når Tag Manager indlæses efter track.js. Desuden tjekkes `dataLayer` hvert sekund i 60 sekunder.
- Andre hændelser (`view_item`, `add_to_cart` …) ignoreres.
- Et køb uden `transaction_id` ignoreres.
- Hvert ordrenummer meldes en gang pr. side; browserens og serverens dubletværn stopper resten.
### Felter, der bruges
| Felt | Bruges til |
|---|---|
| `transaction_id` | Ordrenummeret. Påkrævet. |
| `value` | Ordrens beløb. |
| `shipping` | Fragten, der trækkes fra, når den er med i `value`. |
| `items[].price` og `items[].quantity` | Varelinjernes sum (`quantity` er 1, hvis den mangler). Bruges, når `value` mangler, og til at gætte, om fragten er med. |
| `currency` | Valutaen. Mangler den, bruges den første varelinjes `currency`, ellers `DKK`. |
`tax` læses ikke. Momsen trækkes ud med `data-sale-vat`.
## Sådan regnes beløbet
1. Udgangspunktet er `value`. Er den 0 eller mangler, bruges varelinjernes sum.
2. Fragten trækkes fra, når `shipping` er over 0, og enten `data-sale-shipping="incl"` er sat, eller (uden `excl`) `value` er større end varelinjernes sum – så er fragten regnet med i `value`.
3. Med `data-sale-vat` divideres med 1 + moms/100.
4. Resultatet rundes til to decimaler. Er det ikke over 0, meldes intet, og konsollen viser `Affilyflow: the purchase event has no amount – the sale was not recorded.`
| `value` | `shipping` | Varelinjer | Indstilling | Meldt |
|---|---|---|---|---|
| 675 | 49 | 750 | moms 25 | 540 |
| 799 | 49 | 750 | moms 25 | 600 |
| 724 | 49 | 750 | moms 25, fragt `incl` | 540 |
| 1000 | – | – | moms 25 | 800 |
| 540 | – | – | ingen moms | 540 |
> [!NOTE]
> En rabat, der er større end fragten, kan narre gættet: `value` bliver mindre end varelinjerne, selvom fragten er med. Sæt `data-sale-shipping="incl"` (eller vælg det i Opsætning), hvis jeres `value` altid indeholder fragten.
## Serveren regner igen
Ud over beløbet sender scriptet Google Analytics' rå tal med salget: `value`, `shipping` og varelinjernes sum. Har butikken gemt sin Google Analytics-opsætning under Opsætning i dashboardet (moms og fragt), regner serveren beløbet igen med den – med præcis samme regel som ovenfor.
- En rettelse af moms eller fragt i Opsætning virker fra næste salg. Scriptet skal ikke lægges op igen.
- Serverens beløb bruges kun, hvis det er over 0 og ikke over det, Google Analytics selv sagde. Ellers bruges scriptets beløb.
- Uden en gemt Google Analytics-opsætning bruges scriptets beløb, som attributterne gav.
- Kun salg fra Google Analytics-vejen regnes om. Salg fra salgslinjen, `affilyflow.conversion()` og integrationerne røres ikke.
Live-testen under Opsætning viser det beløb, der kom frem, så I kan se, om moms og fragt er rigtige. Se [Test og fejlfinding](https://www.affilyflow.com/docs/butik/test-og-fejlfinding).
## Samtykke og klik
Google Analytics-vejen følger de samme regler som resten af track.js:
- Uden et affiliateklik i browseren sendes intet.
- Med `data-consent="required"` venter salget, til den besøgende har givet samtykke til marketing. Se [Samtykke og cookies](https://www.affilyflow.com/docs/butik/samtykke-og-cookies).
Kilde: https://www.affilyflow.com/docs/reference/google-analytics
---
# Konverterings-API
> Endpointet, der tager imod salg – til server-side og egne integrationer, med parametre, svar og grænser.
Alle salg fra [track.js](https://www.affilyflow.com/docs/reference/track-js) ender i det samme endpoint. Bygger I selv integrationen – fx fra jeres egen backend, når betalingen er gennemført – kan I kalde det direkte.
```http title="Endpoint"
POST https://app.affilyflow.com/api/track/conversion
GET https://app.affilyflow.com/api/track/conversion
```
- Både `GET` og `POST` virker. `OPTIONS` svarer på CORS-forespørgsler.
- CORS er åben (`Access-Control-Allow-Origin: *`), så endpointet kan kaldes fra enhver browser.
- Der kræves ingen nøgle. Det, der binder salget til en affiliate, er klik-id'et.
## Klik-id'et først
Uden klik-id'et kan salget ikke tilskrives nogen. Det kommer fra affiliate-linket som `?afid=` på jeres side, og track.js gemmer det i cookien `af_click` på jeres domæne. Jeres server kan derfor læse cookien fra kundens anmodning, når ordren oprettes, og sende den med.
Har I ikke track.js på siden, så gem selv `afid` fra adressen. Tag kun værdier, der ligner vores: `c_` efterfulgt af 4–64 tegn (bogstaver, tal, `_` og `-`).
> [!TIP]
> Gem klik-id'et på ordren, når den oprettes. Så kan salget meldes fra serveren, også når betalingen bliver bekræftet senere, og kunden aldrig ser kvitteringen.
## Parametre
| Parameter | Alias | Påkrævet | Betydning |
|---|---|---|---|
| `afid` | `click_id` | Ja | Klik-id'et (`c_…`). |
| `store` | – | Anbefalet | Butikkens id (det samme som `data-store`). Skal passe til klikkets butik, ellers `store_mismatch`. |
| `order` | `order_id` | Anbefalet | Ordrenummeret. Samme nummer tælles aldrig to gange i samme program. Højst 120 tegn. |
| `total` | `order_total` | Ja | Beløbet, provisionen regnes af: varerne efter rabat, uden moms og fragt. Et maskintal (`1249.5`). |
| `currency` | – | Nej | Valutakode efter ISO 4217, fx `DKK` eller `EUR`. Standard er `DKK`. |
> [!WARNING]
> Endpointet læser `total` som et maskintal. `"1.249,00"` er ikke et tal her og bliver til 0. Det er kun track.js, der renser danske beløb – send `1249` eller `1249.00` fra jeres server.
Ældre direkte links uden klik-id kan i stedet sende `aff` (eller `aff_id`) – affiliatens tracking-id – sammen med `store`. Så skal affiliaten være tilmeldt butikkens program, ellers svares `not_joined`.
## GET
Parametrene i adressen. Det er det, track.js bruger som reserve, når browseren ikke har `sendBeacon`.
```bash title="GET"
curl "https://app.affilyflow.com/api/track/conversion?afid=c_AbC123xYz_-9&store=DIN_BUTIK&order=1008&total=499¤cy=DKK"
```
## POST
Send felterne som JSON i kroppen. Felterne kan også stå i adressen – findes et felt begge steder, vinder kroppen. Det er sådan track.js sender med `navigator.sendBeacon`: en `POST` med felterne i adressen og ingen krop.
```bash title="POST med JSON"
curl -X POST https://app.affilyflow.com/api/track/conversion \
-H "Content-Type: application/json" \
-d '{"afid":"c_AbC123xYz_-9","store":"DIN_BUTIK","order":"1008","total":499,"currency":"DKK"}'
```
```js title="Node · efter betalingen"
await fetch('https://app.affilyflow.com/api/track/conversion', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
afid: order.afClick, // gemt fra cookien af_click
store: 'DIN_BUTIK',
order: order.number,
total: order.subtotalExVat, // varer efter rabat, uden moms og fragt
currency: order.currency
})
});
```
Kroppen må højst fylde 64 KB.
### Valgfrie felter i kroppen
| Felt | Betydning |
|---|---|
| `items` | Varelinjer: en liste af `{ title, variant, qty, price, product_id }`. `title` er påkrævet pr. linje. Højst 50 linjer; lange tekster afkortes. |
| `subtotal`, `tax`, `shipping` | Ordrens opdeling (tal, 0 eller større). Se nedenfor. |
| `buyer_hash` | Fingeraftryk af køberens e-mail: `hex(sha256(lowercase(trim(e-mail))))` – 64 hex-tegn. Matcher det affiliatens e-mail, registreres salget som afvist (`self_referral`). E-mailen selv sendes aldrig. |
**Opdelingen:** Sender I `total` som ordrens samlede beløb og `subtotal` med, regnes provisionen af `subtotal` i stedet – dog aldrig af mere end `total`:
- Er `tax` 0 eller mangler, eller er `subtotal + shipping + tax = total` (moms lagt oven i), er grundlaget `subtotal`.
- Er `subtotal + shipping = total` (moms inkluderet i priserne), er grundlaget `subtotal × (total − tax) / total`.
- Ellers bruges det forsigtige `min(subtotal, total − tax − shipping)`.
Det enkleste er at sende `total` som varernes beløb uden moms og fragt og udelade opdelingen.
## Svar
Svaret er altid JSON med en `status`. Hvad hver status betyder, og hvad I gør, står i [Statuskoder](https://www.affilyflow.com/docs/reference/statuskoder).
| HTTP | Krop | Hvornår |
|---|---|---|
| 200 | `{ "status": "ok" }` | Salget er registreret. |
| 200 | `{ "status": "duplicate" }`, `"expired"` m.fl. | Anmodningen blev behandlet, men salget gav ikke provision. |
| 404 | `{ "status": "unknown" }` | Klik-id'et findes ikke. |
| 400 | `{ "status": "error", "reason": "…" }` | Hverken `afid` eller `aff` + `store` var med. |
| 413 | `{ "status": "too_large" }` | Kroppen var over 64 KB. |
| 429 | `{ "status": "rate_limited" }` | For mange kald fra samme IP-adresse. |
| 500 | `{ "status": "error" }` | Registreringen fejlede. Prøv igen. |
Et registreret salg afventer butikkens godkendelse. Provisionen krediteres først, når salget er godkendt.
## Dubletter og ordrenumre
- Samme ordrenummer i samme program tælles aldrig to gange – andet kald svarer `duplicate`. Det er derfor sikkert at sende igen, hvis I er i tvivl.
- Uden ordrenummer er der ingen dubletkontrol på serveren. Send altid ordrenummeret.
- En uudfyldt pladsholder (`DIN_ORDRE_ID`, `ORDER_ID`, `{{ … }}`, `[ … ]`, `< … >`, `% … %` m.fl.) behandles som intet ordrenummer, så den aldrig optager nummeret for alle senere salg.
## Valuta
Salget kan være i enhver valuta med en kurs fra Den Europæiske Centralbank. Det omregnes til butikkens afregningsvaluta, før provision og gebyr regnes, og kursen låses på salget. En valutakode, der ikke er tre bogstaver, eller en valuta uden en kurs fra de seneste 10 dage, afvises som `unsupported_currency`.
## Grænser
- Endpointet er begrænset pr. IP-adresse. Grænsen rammer ikke en butiks egen server ved normal brug; svaret er `429` med `rate_limited`, hvis den nås.
- Hvert program har et beløbsloft pr. ordre (standard 50.000 i afregningsvalutaen). En ordre over loftet afvises som `value_too_high` – typisk et beløb sendt i øre eller cent.
- Lange værdier afkortes i stedet for at blive afvist: klik-id 64 tegn, butik og ordrenummer 120 tegn.
## Refusioner
Endpointet melder kun salg. Refunderes en ordre, afviser butikken salget under Salg i dashboardet inden for programmets godkendelsesperiode. `/api/track/conversion/reverse` bruges af WooCommerce-pluginnet og kræver butikkens nøgle – det er ikke en offentlig vej.
Se også [Specialbygget webshop](https://www.affilyflow.com/docs/butik/specialbygget) og [Test og fejlfinding](https://www.affilyflow.com/docs/butik/test-og-fejlfinding).
Kilde: https://www.affilyflow.com/docs/reference/konverterings-api
---
# Statuskoder
> Hvad hver status fra konverteringsendpointet betyder – og hvad I gør ved den.
Hvert salg, der meldes til Affilyflow, får en status. I ser den i svaret fra [konverteringsendpointet](https://www.affilyflow.com/docs/reference/konverterings-api) (`{ "status": "…" }`) og i live-testen under Opsætning i butikkens dashboard. Mange statusser er helt normale – kun nogle betyder, at opsætningen skal rettes.
## Registreret
| Status | Betydning | Hvad I gør |
|---|---|---|
| `ok` | Salget er registreret og tilskrevet en affiliate – eller butikkens testlink, hvis det var et testkøb. Det afventer nu butikkens godkendelse. | Intet. |
## Normalt – ikke talt
| Status | Betydning | Hvad I gør |
|---|---|---|
| `duplicate` | Samme ordrenummer kom igen i samme program og blev ikke talt to gange – fx en genindlæst kvittering. | Intet. Kommer det ved hvert salg, så tjek, at ordrenummeret ikke er det samme hver gang. |
| `expired` | Klikket er ældre end programmets cookie-vindue. Vinduet låses på klikket. | Intet. Ved en test: åbn testlinket igen, og køb lige efter. |
| `unknown` | Klik-id'et findes ikke – slettet, forkert eller aldrig registreret. HTTP 404. | Tjek, at I sender klik-id'et uændret (`c_…`). Ved en test: åbn testlinket igen, og køb i samme browser. |
| `blocked` | Butikken har blokeret affiliaten, så salget gav ingen provision. | Intet. |
| `suspended` | Affiliaten er suspenderet, så salget gav ingen provision. | Intet. |
| `store_inactive` | Butikken er sat inaktiv, så salget gav ingen provision. | Skriv til info@affilyflow.com, hvis det er en fejl. |
## Ret opsætningen
| Status | Betydning | Hvad I gør |
|---|---|---|
| `store_mismatch` | Klikket hører til en anden butik end den, salget blev meldt for. | Tjek, at `data-store` (eller `store`) er jeres eget butiks-id fra Opsætning. |
| `invalid_value` | Beløbet var negativt. | Send varernes beløb efter rabat, uden moms og fragt, som et positivt tal. |
| `zero_value` | Programmet betaler et fast beløb pr. salg, men ordren var på 0. | Tjek, at beløbet kommer med. På kvitteringen: se feltet med beløbet. |
| `value_too_high` | Ordren var over programmets beløbsloft (standard 50.000 i afregningsvalutaen) og blev ikke registreret. | Tjek, at beløbet ikke er sendt i øre eller cent. Er det et rigtigt salg, skriv til info@affilyflow.com. |
| `unsupported_currency` | Valutaen kunne ikke omregnes: koden er ikke tre bogstaver, eller der er ingen kurs fra de seneste 10 dage. | Send en ISO 4217-kode som `DKK` eller `EUR`. Er koden rigtig, skriv til info@affilyflow.com. |
| `stale_lead_trigger` | Et lead (ordrenummer der begynder med `lead_`) blev meldt til et program, der betaler pr. salg. | Fjern lead-koden eller `data-lead-url`, og brug salgskoden. |
| `program_inactive` | Programmet var sat på pause, da salget kom. | Slå programmet til igen, hvis pausen ikke var meningen. |
| `not_joined` | Kun ved ældre links uden klik-id: affiliaten er ikke tilmeldt programmet. | Intet – affiliaten skal bruge et link fra sit dashboard. |
## Afvist efter reglerne
Disse salg registreres, men står som afvist med årsagen – uden provision, gebyr eller notifikationer.
| Status | Betydning | Hvad I gør |
|---|---|---|
| `self_referral` | Køberens e-mail er den samme som affiliatens (handelsbetingelserne §7). Kræver, at integrationen sender `buyer_hash`. | Intet. |
| `rule_breach` | Klikket brød programmets regler – rabatkodeside, forbudt betalt søgning eller en kanal, programmet ikke tillader. Kun når Affilyflow håndhæver programreglerne. | Intet. Butikken ser bruddet under Regelbrud. |
## Fejl fra endpointet
| Status | HTTP | Betydning | Hvad I gør |
|---|---|---|---|
| `error` | 400 | Hverken klik-id eller `aff` + `store` var med. | Send `afid`. |
| `error` | 500 | Registreringen fejlede. Salget er måske ikke gemt. | Prøv igen med samme ordrenummer – en dublet tælles ikke to gange. |
| `rate_limited` | 429 | For mange kald fra samme IP-adresse. | Vent lidt, og prøv igen. |
| `too_large` | 413 | Kroppen var over 64 KB. | Send færre varelinjer. |
## Fra integrationerne
Stripe-webhooken, WooCommerce-pluginnet og Shopify-appen melder også salg, og disse statusser kan ses i deres hændelser:
| Status | Betydning |
|---|---|
| `no_affiliate` | Salget kom uden klik eller affiliate – et almindeligt salg. |
| `not_recurring` | En abonnementsfornyelse, hvor klikket ikke gav ret til abonnementsprovision. |
| `first_rejected` | En fornyelse af et abonnement, hvis første salg er afvist – ingen provision. |
| `cap_reached` | Abonnementet har nået det antal betalinger, der gives provision for. |
| `test_mode` | Stripe i testtilstand bogføres ikke. |
| `unpaid` | Betalingen var ikke gennemført. |
| `unattributed` | Shopify: et køb uden klik-id, mens butikken tester. Kun tidspunktet gemmes, og det krediteres aldrig. |
For Stripe er `unknown` og `expired` almindelige: en checkout uden affiliate har intet klik.
Se [Test og fejlfinding](https://www.affilyflow.com/docs/butik/test-og-fejlfinding) for at finde årsagen trin for trin.
Kilde: https://www.affilyflow.com/docs/reference/statuskoder
---
# Links og sub-ID
> Sådan laver du sporbare links, deeplinks til bestemte varer, sub-ID'er og HTML-kode, der viser den præcise side, hvert klik kom fra.
Et Affilyflow-link sender kunden videre til butikken og registrerer klikket. Kommer der et køb inden for programmets cookie-varighed, får du provisionen. Satsen og cookie-vinduet låses i det øjeblik, der klikkes – en butik, der senere sænker satsen, rammer ikke klik fra før.
## Sådan ser et link ud
```text title="Dit link"
https://app.affilyflow.com/go/3f9a1c07be
```
Den sidste del er linkets kode. Adressen ændrer sig aldrig – heller ikke, når du retter eller arkiverer linket.
## Opret et link
Tryk **Opret link** på Oversigt, under **Links → Dine links** eller under **Værktøjer**. Du kan også lave et link fra en programside eller direkte fra en vare under **Programmer → Produkter**. Du kan kun lave links til programmer, du er tilmeldt.
| Felt | Det gør |
|---|---|
| Program | Den butik, linket går til. |
| Hvor skal kunden lande? | Forsiden eller en bestemt side hos butikken (deeplink). |
| Sub-ID | Din egen etiket til at skelne placeringer, fx to opslag eller videoer. Højst 80 tegn. |
| Kampagne | Bliver linkets navn og sendes til butikken som `utm_campaign`. |
| Kanal | Fx SEO, Blog, Instagram, TikTok, YouTube, Facebook, Pinterest, E-mail eller Website. Kun programmets tilladte kanaler kan vælges. Sendes til butikken som `utm_medium`. |
| Site | Valgfri etiket. Sendes til butikken som `utm_source`. Behøves ikke – klik fra dine sites måles automatisk. |
| Placering | Den side, hvor du sætter linket. Bruges kun, når browseren ikke selv sender siden. |
Findes der allerede et link med præcis samme indstillinger, får du det eksisterende link i stedet for en kopi. Ændrer du landingsside, sub-ID, kampagne eller kanal, bliver det et nyt link.
## Deeplinks til en bestemt side
Under **Hvor skal kunden lande?** kan du skrive:
- en sti, fx `/produkter/sofa`
- en sti med butikkens egne parametre, fx `/produkter/sofa?variant=42`
- hele adressen fra butikken, fx `https://butik.dk/produkter/sofa`
Landingssiden skal ligge på butikkens eget domæne – ellers kan salget ikke spores, og linket bliver ikke lavet.
Du kan også sende et eksisterende link til en anden side uden at lave et nyt link. Sæt `?p=` på med stien:
```text title="Samme link til en anden side"
https://app.affilyflow.com/go/3f9a1c07be?p=/produkter/sofa
```
En sti på et andet domæne ignoreres, og kunden lander på linkets egen landingsside.
## Sub-ID
Et sub-ID skelner placeringer og kampagner fra hinanden i dine tal – fx en artikel fra et nyhedsbrev, eller to TikTok-videoer. Du kan give et link et fast sub-ID, når du opretter det, eller sætte et på et eksisterende link med `?sid=`:
```text title="Sub-ID pr. placering"
https://app.affilyflow.com/go/3f9a1c07be?sid=tiktok-sofa-video-okt
```
`?sid=` på linket vinder over linkets eget sub-ID. Sub-ID'et gemmes på hvert klik, og du ser det under **Resultater** (Overblik, Klik & salg og Salg), i salgsdetaljen og i CSV-eksporten.
> [!TIP]
> Brug en fast navngivning, så tallene er lette at læse: `--`, små bogstaver og bindestreger, fx `blog-bedste-sofaer-okt`.
## Parametre, du selv kan sætte på linket
| Parameter | Eksempel | Virkning |
|---|---|---|
| `sid` | `?sid=blog-sofaer-okt` | Sub-ID for netop dette klik (højst 80 tegn). |
| `p` | `?p=/produkter/sofa` | Landingsside hos butikken. |
| `utm_*` | `?utm_content=knap-top` | Sendes videre til butikken, så den kan se det i sin egen analyse. |
Parametrene kan kombineres: `?sid=nyhedsbrev-okt&p=/udsalg`. Parameteren `placement` sættes af scriptet [placement.js](https://www.affilyflow.com/docs/affiliate/side-sporing) – den skal du ikke selv skrive.
## Kopier link eller Kopier HTML
Hvert link har to knapper.
**Kopier link** giver adressen. Brug den i sociale medier, videobeskrivelser, mails og andre steder, hvor du kun kan indsætte en adresse.
**Kopier HTML** giver en færdig linkkode til dit site. Du kan rette linkteksten, før du kopierer.
```html title="Kopier HTML"
Se tilbuddet hos Butik
```
| Attribut | Hvorfor |
|---|---|
| `referrerpolicy="no-referrer-when-downgrade"` | Beder browseren sende hele sidens adresse med. Så får klikket automatisk den præcise side, linket stod på – uden script og uden sub-ID. |
| `rel="sponsored"` | Fortæller søgemaskiner, at linket er et betalt link. |
| `rel="noopener"` og `target="_blank"` | Åbner butikken i en ny fane, uden at butikkens side kan styre din. |
Bannere fra butikken (på programsiden, når du er tilmeldt) har de samme attributter.
> [!NOTE]
> Har du allerede sat almindelige links ind på dit site, får du den præcise side ved at skifte dem ud med HTML-koden – eller ved at sætte scriptet ind en gang. Se [Side-sporing](https://www.affilyflow.com/docs/affiliate/side-sporing).
## QR-kode
Åbn et link under **Dine links** for at se og hente dets QR-kode – fx til tryksager, plakater eller en video.
## Ret et link
Under **Dine links** kan du ændre et links navn, landingsside og site-etiket. Adressen ændrer sig aldrig.
Sub-ID, kampagne og kanal kan ikke ændres på et eksisterende link, fordi klikkene allerede er registreret med dem. Lav et nyt link, eller sæt `?sid=` på det eksisterende.
## Arkiver et link
Tryk **Arkiver** for at skjule et link fra listen. Et arkiveret link virker stadig: det sender kunden videre og registrerer klik og salg. Tryk **Hent frem** for at vise det igen.
## Grænser
- Links kan kun laves til programmer, du er tilmeldt, og som er aktive.
- Via [API'et](https://www.affilyflow.com/docs/affiliate/rest-api) og [MCP-serveren](https://www.affilyflow.com/docs/affiliate/mcp) kan du lave højst 500 nye links pr. døgn. Et eksisterende identisk link, der genbruges, tæller ikke med.
> [!WARNING]
> En rabatkode giver ikke provision i sig selv – det er klikket på dit link, der krediterer dig. Del altid koden sammen med linket.
Kilde: https://www.affilyflow.com/docs/affiliate/links-og-sub-id
---
# Side-sporing
> Se præcis hvilken side på dit site hvert klik kom fra – automatisk med HTML-koden, med placement.js eller gennem din egen videresendelse.
Affilyflow gemmer på hvert klik, hvilket site og hvilken side linket stod på. Du skal ikke tagge dine links med et site eller et sub-ID for at få det – det måles. Tallene står under **Links → Mine sites**, **Links → Placeringer** og **Resultater → Klik & salg**.
## Site og side
**Sitet** kommer fra domænet på den side, klikket kom fra. Det kender vi næsten altid, fordi browseren som standard sender domænet med. Søgemaskiner, sociale netværk, webmail og Affilyflow selv er kanaler – ikke sites – og giver aldrig et site eller en side.
**Den præcise side** (fx `minside.dk/bedste-sofaer`) sender browseren kun med, hvis siden beder om det. Uden en af rettelserne herunder ser du derfor kun domænet.
## Sådan kender vi siden
Hvert klik viser, hvordan siden blev kendt:
| Mærke i dashboardet | Det betyder |
|---|---|
| Præcis side · script | placement.js satte siden på linket. Virker også i Safari og apps. |
| Præcis side · målt | Browseren sendte hele adressen – typisk fordi linket er sat ind med vores HTML. |
| Via din videresendelse | Klikket gik gennem din egen videresendelse, fx `/go/` eller `/anbefaler/`. Det er den side, vi får. |
| Forsiden · målt | Browseren sendte sitets forside, og samme site sender hele adressen for sine andre sider – så klikket kom fra forsiden. |
| Angivet på linket | Du skrev selv siden på linket. Det er din oplysning, ikke en måling. |
| Kun domæne | Browseren sendte kun domænet. Det kan være forsiden eller en side, hvor browseren skar adressen ned. |
| Ingen side | Klikket kom ikke fra et site, fx fra sociale medier, en app, en mail eller uden henviser. |
## Rettelse 1: placement.js (anbefalet)
Indsæt scriptet en gang på hele sitet. Det dækker alle dine Affilyflow-links – også dem, der allerede står på sitet – og virker i Safari og apps.
```html title="Indsæt en gang på hele sitet"
```
Hvor du indsætter det:
- **WordPress:** WPCode → Add Snippet → HTML → Site Wide Footer.
- **Webflow:** Site settings → Custom code → Footer code.
- **Andet:** lige før `` i sidens skabelon.
Sådan virker scriptet:
- Det finder links, der peger direkte på et Affilyflow-link (`/go/…`), og sætter sidens adresse på dem som `?placement=` – uden query og hash.
- Det giver de samme links `referrerpolicy="no-referrer-when-downgrade"`, hvis de ikke har en.
- Links, der kommer til senere (uendelig scroll, popups, sider der skifter uden at genindlæse), bliver også tagget.
Scriptet sætter ingen cookies, sporer ikke dine besøgende og kalder ikke hjem. Det ændrer kun adressen på dine Affilyflow-links.
## Rettelse 2: HTML-links
Tryk **Kopier HTML**, når du laver et link – i link-generatoren, på et link under Dine links eller i produktsøgningen – og sæt koden ind i stedet for et almindeligt link.
```html title="Linket med henvisningspolitik"
Se tilbuddet hos Butik
```
`referrerpolicy` beder browseren sende hele sidens adresse med. Det virker i Chrome, Edge og Firefox uden noget script. Links, du allerede har sat ind, skal skiftes ud for at virke. Se [Links og sub-ID](https://www.affilyflow.com/docs/affiliate/links-og-sub-id).
## Rettelse 3: WordPress uden plugin
Har du mange gamle links i dine indlæg, kan et lille filter give dem alle den rigtige henvisningspolitik, uden at du ændrer dem. Indsæt det i `functions.php` eller som et PHP-snippet (fx i WPCode).
```php title="functions.php eller et PHP-snippet"
add_filter('the_content', function ($html) {
return preg_replace(
'#]*referrerpolicy)([^>]*href="https://(?:app|go)\.affilyflow\.com/go/)#i',
' [!NOTE]
> placement.js tagger kun links, der peger direkte på Affilyflow. Links til din egen videresendelse tagges ikke, og siden, linket stod på, kan vi derfor ikke se bag videresendelsen. Vil du have den præcise artikel, så brug Affilyflow-linket direkte med HTML-koden eller scriptet.
## Angivet side på linket
Når du laver et link, kan du skrive den side, hvor du sætter det (**Placering**). Den bruges kun, når browseren ikke selv sender siden, og den ignoreres, hvis browseren viser, at klikket kom fra et andet site. Den vises som **Angivet på linket** – ikke som målt.
## Dækning
Under **Mine sites** (og i panelet på Dine links og Placeringer) ser du for hvert site, hvor stor en del af klikkene fra sitet de seneste 30 dage der har en målt side (script, målt henvisning, forsiden eller din videresendelse). Fra 80 % står dækningen som grøn. Ligger den lavere, viser panelet den ene rettelse, der passer til sitet. Er dækningen høj, kan du skjule panelet.
## Ærligt om browsere
- **Chrome, Edge og Firefox** sender hele adressen, når linket har `referrerpolicy="no-referrer-when-downgrade"`.
- **Safari og nogle apps** sender stadig kun domænet – også med HTML-koden. Kun scriptet giver den præcise side der.
- Uden HTML-koden, scriptet eller en videresendelse ser du sitet, men ikke siden.
Siden er kun adressen på din egen side. Den siger intet om den besøgende.
## Sub-ID er stadig nyttigt
Side-sporingen fortæller dig hvilken side et klik kom fra. Et [sub-ID](https://www.affilyflow.com/docs/affiliate/links-og-sub-id) skelner det, siden ikke kan – fx to links i samme artikel, et nyhedsbrev eller en video.
Kilde: https://www.affilyflow.com/docs/affiliate/side-sporing
---
# Programregler
> Godkendelsesperiode, kanaler, betalt søgning, rabatkodesider og hvad der sker, hvis en regel brydes – og hvordan regelændringer varsles.
Programmerne på Affilyflow er åbne: du tilmelder dig uden ansøgning, og butikken skal ikke godkende dig. Til gengæld har hvert program klare regler, og Affilyflow holder øje med, at de overholdes. Reglerne står på programsiden under **Regler og kanaler**.
## Godkendelsesperioden
Et nyt salg står som **Afventer**. Butikken kan afvise det med en årsag inden for programmets godkendelsesperiode – fx hvis varen bliver sendt retur. Afviser butikken ikke, godkendes salget automatisk, når perioden er gået.
- Butikken vælger 30, 45, 60 eller 90 dage – typisk det samme som sin returret.
- Perioden låses på salget, når det registreres. Ændrer butikken perioden senere, gælder det kun nye salg.
- Salgsdetaljen viser datoen, hvor salget forventes godkendt.
Refunderes en ordre gennem butikkens WooCommerce-plugin eller Stripe, afvises salget automatisk. Ved en delvis refusion nedsættes en provision i procent i samme forhold, mens en fast provision pr. salg ikke ændres.
## Kanaler
Programmet kan begrænse, hvor du må promovere butikken: SEO, Blog, Instagram, TikTok, YouTube, Facebook, Pinterest og E-mail. Står der **Alle kanaler er tilladt**, er der ingen begrænsning.
Kanalen måles på klikket ud fra henviseren – fx et klik fra instagram.com eller tiktok.com. Kommer et klik fra en kanal, programmet ikke tillader, er det et brud.
## Betalt søgning
| Regel | Det betyder |
|---|---|
| Tilladt | Du må annoncere i Google Ads og lignende – også på butikkens brandnavn. |
| Ikke på butikkens brandnavn | Du må annoncere i Google Ads og lignende, men ikke byde på butikkens navn eller varemærke. |
| Ikke tilladt | Du må ikke bruge betalt søgning til at sende trafik til butikken. |
Standarden for et nyt program er **Ikke på butikkens brandnavn**.
## Rabatkode- og cashbacksider
Står reglen til **Ikke tilladt** (standard), må du ikke lægge butikken på sider, der samler rabatkoder eller giver penge tilbage ved køb. Affilyflow kender en liste over den slags sider, og klik derfra er et brud.
## Rabatkoder
Deler butikken rabatkoder med affiliates, står de på programsiden. En kode giver ikke provision i sig selv – det er klikket på dit link, der krediterer dig. Del koden sammen med linket.
## Butikkens retningslinjer
Ud over de faste regler kan butikken skrive sine egne retningslinjer i fritekst, fx om tone, billeder eller ord, du ikke må bruge. Dem vurderer Affilyflow ikke automatisk – men butikken kan afvise salg, der bryder dem, inden for godkendelsesperioden.
## Når reglerne ændres
Ændringer skal være fair over for dig, der allerede promoverer programmet:
- **En stramning** – fx en kanal, der fjernes, eller rabatkodesider, der ikke længere er tilladt – gælder først 14 dage efter, at butikken har gemt den. Indtil da gælder de tidligere regler, og programsiden viser **Nye regler fra** med datoen, så du kan nå at rette dine placeringer til.
- **En lempelse** gælder med det samme.
- Hvert klik bedømmes efter de regler, der gjaldt, da der blev klikket. Dommen gemmes på klikket, så en senere stramning aldrig rammer klik fra før.
Er du tilmeldt programmet, får du besked, når butikken ændrer reglerne eller vilkårene.
## Sådan holder vi øje
Regelvagten ser på hvert klik, når det sker – uden ekstra kald og uden at gemme mere end nødvendigt:
- **Annonceklik:** klik-id'er fra Google Ads og Microsoft Ads (fx `gclid` og `msclkid`) og `utm_medium=cpc`, `ppc`, `paid` eller `sem`.
- **Rabatkode- og cashbacksider:** henvisninger fra kendte sider af den slags.
- **Kanal:** den kanal, henviseren peger på – Instagram, Facebook, TikTok, YouTube, Pinterest eller E-mail.
Søgeordet i en annonce kan vi ikke se. Derfor kan et annonceklik i et program med reglen **Ikke på butikkens brandnavn** ikke afgøres automatisk.
## Hvad sker der ved et brud
| Brud | Følge, når reglerne håndhæves |
|---|---|
| Klik fra en rabatkode- eller cashbackside, når det ikke er tilladt | Salget registreres som afvist: *Regelbrud: klik fra rabatkode- eller cashbackside*. |
| Annonceklik, når betalt søgning ikke er tilladt | Salget registreres som afvist: *Regelbrud: betalt søgning er ikke tilladt i programmet*. |
| Klik fra en kanal, programmet ikke tillader | Salget registreres som afvist: *Regelbrud: kanalen er ikke tilladt i programmet*. |
| Annonceklik, når betalt søgning er tilladt, men ikke på brandnavnet | Salget afventer som normalt, men godkendes ikke automatisk. Affilyflow vurderer det. |
Et afvist salg giver ingen provision. Har du sikre brud på 3 forskellige dage inden for 30 dage på samme program, lukkes programmet automatisk for dig.
Regelvagten kan stå i to tilstande:
- **Måling:** brud registreres, men ingen salg afvises, og intet program lukkes.
- **Håndhævelse:** som i tabellen ovenfor. Kun klik fra og med skiftet håndhæves – et klik fra måleperioden koster aldrig provision bagefter.
Programsiden viser, hvilken tilstand der gælder. Står der **Reglerne håndhæves automatisk**, afvises salg fra brud. Har vi set brud på dine klik de seneste 30 dage, står de på programsiden under **Klik, der ikke giver provision**.
> [!TIP]
> Er et salg afvist med et regelbrud, du mener er forkert, så skriv til support. Afvisningen kan vurderes af et menneske.
## Selvhenvisning
Køb via dit eget link giver ikke provision. Kan butikkens integration se, at køberen og du er samme person, registreres salget som afvist med årsagen *Selvhenvisning*. Det gælder WooCommerce-plugin og Stripe, som sender et fingeraftryk af køberens e-mail – aldrig selve e-mailen.
## Marker reklame
Indhold med affiliate-links er reklame og skal være tydeligt markeret efter markedsføringsloven – fx med "Reklame" eller "Annonce" øverst eller #reklame på sociale medier.
## Tjek før du går i gang
Bruger du en AI-assistent, kan værktøjet `check_compliance` tjekke en planlagt promovering mod programmets målbare regler og svare ok, advarsel eller ikke tilladt pr. regel. Se [MCP-server](https://www.affilyflow.com/docs/affiliate/mcp) og [REST-API](https://www.affilyflow.com/docs/affiliate/rest-api).
Kilde: https://www.affilyflow.com/docs/affiliate/regler
---
# Udbetaling
> Automatisk udbetaling den første bankdag i måneden fra 100 kr – bankoplysninger, stamdata, selvfakturering, moms og hvad der sker ved refusioner.
Godkendt provision udbetales automatisk den første bankdag i måneden, når du har mindst 100 kr til rådighed, og dine oplysninger er på plads. Du kan også selv trykke **Udbetal nu**. Alt foregår under **Konto → Udbetaling**.
## Sådan virker det
- **Automatisk:** den første bankdag i måneden. Weekender og bankernes lukkedage springes over – fx er udbetalingsdagen i november 2026 mandag 2. november.
- **Minimum:** 100 kr til rådighed. Det gælder både den automatiske udbetaling og **Udbetal nu**.
- **Udbetal nu:** du kan bede om udbetaling, når som helst minimum og oplysninger er på plads.
- **Pause:** du kan sætte den automatiske udbetaling på pause og bruge **Udbetal nu**, når du vil have pengene.
Beløbet regnes af serveren ud fra din saldo. Du får en besked i dashboardet, når udbetalingen er på vej, og hver udbetaling får et afregningsbilag. Selve overførslen laver Affilyflow til den bankkonto, du har opgivet.
## Saldoen
| Begreb | Det betyder |
|---|---|
| Til rådighed | Godkendt provision, der ikke er udbetalt. Det er det, der kan udbetales. |
| Afventer | Provision fra salg, der endnu ikke er godkendt. Godkendes efter programmets godkendelsesperiode. |
| Undervejs | Udbetalinger, der er oprettet, men ikke gennemført. |
| Udbetalt | Det, du har fået overført. |
- Kun godkendt provision kan udbetales.
- Saldoen er altid uden moms.
- Saldo og udbetaling er i danske kroner. Salg i andre valutaer omregnes med en kurs fra ECB, der låses på salget.
- Saldoen kan blive negativ, hvis et salg refunderes, efter at provisionen er udbetalt. Se [Refusioner efter udbetaling](#refusioner-efter-udbetaling).
## Det skal være på plads
Siden **Udbetaling** viser en tjekliste over det, der mangler. De punkter, du selv kan ordne:
| Mangler | Hvad du gør |
|---|---|
| Bankoplysninger | Udfyld reg.nr. og kontonummer – eller et IBAN. |
| Stamdata | Udfyld type, navn, adresse og land under Stamdata. |
| Dit CPR-nummer | Kræves for danske privatpersoner og personligt ejede virksomheder uden momsregistrering. |
| Dit skatte-id (TIN) | Kræves for privatpersoner og virksomheder i et andet EU-land. |
| Din fødselsdato | Kræves for privatpersoner i et andet EU-land. |
| Aftalen om selvfakturering | Læs og accepter den under Stamdata. |
Andre punkter venter af sig selv: 48 timer efter et bankskift, kontrol af dit momsnummer og en udbetaling, der allerede er undervejs.
## Bankoplysninger
Har du en dansk konto, skriver du reg.nr. og kontonummer – dashboardet laver IBAN'et for dig. Du kan også skrive et IBAN direkte. Et dansk IBAN er `DK` efterfulgt af 16 cifre.
> [!WARNING]
> Skifter du bankkonto, får du en sikkerhedsmail, og vi udbetaler først til den nye konto 48 timer efter ændringen. Får du en mail om et bankskift, du ikke selv har lavet, så kontakt support med det samme.
## Stamdata
Under **Udbetaling → Stamdata** vælger du, hvad du er:
- dansk privatperson
- dansk virksomhed
- udenlandsk privatperson
- udenlandsk virksomhed
Stamdata og aftalen kan gemmes uden CPR-nummer. CPR-nummeret skal først bruges, før der kan udbetales. CPR- og TIN-numre gemmes krypteret.
Udbetalinger til danske privatpersoner indberettes til Skattestyrelsen (eIndkomst). Under **Årsoversigt** finder du en oversigt over året til din selvangivelse.
## Aftale om selvfakturering
Affilyflow køber din markedsføringsydelse og laver bilaget for dig. Derfor skal du acceptere aftalen om selvfakturering, før første udbetaling.
- Hver udbetaling får et afregningsbilag med et fortløbende nummer, fx `SF-2026-0012`.
- Bilaget kan åbnes fra udbetalingen. Er der fejl i det, kan du gøre indsigelse direkte på bilaget inden for 14 dage.
## Moms
| Du er | Moms på udbetalingen |
|---|---|
| Dansk momsregistreret virksomhed med et momsnummer, der er bekræftet i EU's momsregister (VIES) | 25 % moms lægges oven i udbetalingen. |
| Virksomhed i et andet EU-land | Omvendt betalingspligt – ingen moms på udbetalingen. |
| Alle andre – også et momsnummer, der ikke kan bekræftes | Ingen moms. |
Moms lægges kun oven i, når nummeret er bekræftet i VIES – ikke alene fordi du har sat kryds i "momsregistreret". Når du har optjent 40.000 kr i året uden momsregistrering, viser dashboardet en besked om momsgrænsen.
## Refusioner efter udbetaling
Refunderes et salg, efter at provisionen er udbetalt, gælder:
- **Godkendt for højst 90 dage siden:** provisionen modregnes i din næste udbetaling. Saldoen kan blive negativ i mellemtiden.
- **Godkendt for mere end 90 dage siden:** en refusion, som butikkens integration melder, nedsætter ikke din udbetalte provision. Affilyflow bærer forskellen.
En delvis refusion nedsætter en provision i procent i samme forhold. En fast provision pr. salg ændres ikke af en delvis refusion – kun hvis hele ordren refunderes.
## Status på en udbetaling
| Status | Det betyder |
|---|---|
| Afventer | Udbetalingen er oprettet og venter på overførslen. |
| Gennemført | Beløbet er overført, og afregningsbilaget er udstedt. |
| Tilbageholdt | Ved en konkret mistanke om snyd kan vi holde en udbetaling tilbage i op til 60 dage – med en begrundelse. |
| Annulleret eller Afvist | Udbetalingen blev ikke gennemført. Beløbet står igen på din saldo. |
## Se det via API eller AI-assistent
Værktøjerne `get_balance` og `list_payouts` viser saldo, næste udbetalingsdag, hvad der mangler, og dine udbetalinger med bilagsnummer. De kan kun læse – de kan ikke bede om udbetaling, og de viser aldrig bank-, CPR-, skatte- eller adresseoplysninger. Se [REST-API](https://www.affilyflow.com/docs/affiliate/rest-api) og [MCP-server](https://www.affilyflow.com/docs/affiliate/mcp).
Kilde: https://www.affilyflow.com/docs/affiliate/udbetaling
---
# REST-API
> Affiliate-API'et under /api/v1 – nøgler, grænser, alle endpoints med parametre og svar, paginering og fejlkoder.
REST-API'et giver dine egne scripts og integrationer adgang til det samme som dashboardet: programmer, regler, produkter, links, sites, klik, salg, saldo og udbetalinger. Hvert endpoint svarer til et værktøj på [MCP-serveren](https://www.affilyflow.com/docs/affiliate/mcp) – det er de samme funktioner bag begge.
API'et er kun for affiliates. Butikker har intet API.
## Kom i gang
1. Åbn **AI og API** i dashboardets menu (under Tjen penge – adressen er `app.affilyflow.com/indstillinger/api`).
2. Opret en API-nøgle, og giv den et navn, fx "Mit site".
3. Kopier nøglen med det samme. Den vises kun en gang og gemmes kun som et fingeraftryk hos os.
Send nøglen i `Authorization`-headeren på hvert kald:
```bash title="Hent dine programmer"
curl https://app.affilyflow.com/api/v1/programs \
-H "Authorization: Bearer $AFFILYFLOW_API_KEY"
```
Headeren `X-API-Key: afk_…` virker også. En nøgle starter altid med `afk_`.
> [!WARNING]
> Nøglen giver adgang til dine links og tal. Gem den på serveren som en miljøvariabel – aldrig i kode, der kører i browseren, eller i et offentligt repository. Tilbagekald en nøgle under **AI og API**, hvis den er blevet delt. Du kan have højst 10 aktive nøgler.
`GET https://app.affilyflow.com/api/v1` kræver ingen nøgle og svarer med en maskinlæsbar oversigt over alle endpoints, parametre og grænser.
OAuth-forbindelser (claude.ai, ChatGPT, Claude Code) virker kun på MCP-serveren – ikke på REST-API'et.
## Konventioner
| Emne | Regel |
|---|---|
| Basisadresse | `https://app.affilyflow.com/api/v1` |
| Format | JSON ind og ud. Send `Content-Type: application/json` med en body. |
| Beløb | Danske kroner uden moms. Salg i andre valutaer er omregnet med en låst ECB-kurs; originalen står i `original_currency` og `original_value`. |
| Datoer | `from` og `to` som `ÅÅÅÅ-MM-DD` i dansk tid (Europe/Copenhagen), begge dage med. Uden dem: de seneste 30 dage. Højst 366 dage pr. kald. |
| Id'er | Programmer, links, sites og salg har UUID'er. `click_id` er et heltal. |
| Paginering | `limit` + `cursor`. Svaret har `total`, `has_more` og `next_cursor`. Send `next_cursor` tilbage som `cursor` med de samme filtre. |
| Fejl | `{ "error": { "code": "…", "message": "…" } }` med en HTTP-status. Beskederne er på dansk. |
| Cache | Svarene sendes med `Cache-Control: no-store`. |
Afviste salg tæller aldrig som salg eller provision i tallene.
## Grænser
| Grænse | Værdi | Fejl |
|---|---|---|
| Kald pr. minut pr. nøgle | 120 | HTTP 429 `rate_limited` med `Retry-After: 60` |
| Skrivende kald pr. minut pr. nøgle | 60 (et batch-kald tæller som et) | HTTP 429 `rate_limited` |
| Nye links pr. døgn pr. konto | 500 (alle nye links på kontoen de seneste 24 timer tæller med) | HTTP 429 `daily_link_limit` |
| Links pr. batch | 50 | HTTP 400 `invalid_input` |
Et identisk link, der genbruges, tæller ikke som et nyt link. Brug `?sid=` og `?p=` på et eksisterende link i stedet for at lave mange næsten ens links – se [Links og sub-ID](https://www.affilyflow.com/docs/affiliate/links-og-sub-id).
## Endpoints
| Metode | Sti | Bruges til | MCP-værktøj |
|---|---|---|---|
| GET | `/account` | Din konto, kvote og links til dashboardet | `get_account` |
| GET | `/catalog` | Søg i alle åbne programmer | `search_programs` |
| GET | `/programs` | Programmer, du er tilmeldt | `list_programs` |
| GET | `/programs/{id}` | Et program med regler og retningslinjer | `get_program` |
| GET, POST | `/programs/{id}/compliance` | Tjek en promovering mod reglerne | `check_compliance` |
| GET | `/products` | Søg i butikkernes varer | `search_products` |
| GET | `/websites` | Dine registrerede sites | `list_websites` |
| POST | `/websites` | Tilføj et site | `add_site` |
| PATCH | `/websites/{id}` | Ret et site | `update_site` |
| GET | `/links` | Dine links | `list_links` |
| POST | `/links` | Opret et link | `create_link` |
| POST | `/links/batch` | Op til 50 links i et kald | `create_links_batch` |
| GET | `/links/{id}` | Et link med tal | `get_link` |
| PATCH | `/links/{id}` | Ret navn, landingsside eller site-etiket | `update_link` |
| POST | `/links/{id}/archive` | Arkiver et link | `archive_link` |
| DELETE | `/links/{id}/archive` | Hent et arkiveret link frem | `restore_link` |
| GET | `/stats` | Klik, salg og provision for en periode | `get_stats` |
| GET | `/sales` | Enkelte salg | `list_sales` |
| GET | `/clicks` | Enkelte klik med side og kilde | `list_clicks` |
| GET | `/sites` | Målte sites | `get_sites` |
| GET | `/pages` | Sider på dine sites | `get_pages` |
| GET | `/balance` | Saldo og næste udbetaling | `get_balance` |
| GET | `/payouts` | Dine udbetalinger | `list_payouts` |
## Konto
### GET /account
Svarer med `account` (AF-nummer, visningsnavn og hvordan butikkerne ser dig, land, niche, antal sites, links og tilmeldte programmer, valuta og tidszone), `access`, `quota` (links de seneste 24 timer, resterende, grænser og kald brugt det seneste minut) og `dashboard` med adresser til dashboardets sider.
## Programmer
### GET /catalog
Hele kataloget af åbne programmer – også dem, du ikke er tilmeldt.
| Parameter | Type | Beskrivelse |
|---|---|---|
| `query` | tekst | Fritekst i butiksnavn, kategori, beskrivelse og målgruppe. |
| `category` | tekst | Kategori, fx `Bolig`. Svaret har listen i `categories`. |
| `joined` | `true` / `false` | Kun tilmeldte eller kun ikke-tilmeldte. Udelad for begge. |
| `min_commission_pct` | tal 0–100 | Mindste provision i procent. Programmer med fast beløb udelades. |
| `recurring_only` | `true` / `false` | Kun programmer med provision på abonnementsfornyelser. |
| `channel` | tekst | Kun programmer, der tillader kanalen, fx `TikTok`. |
| `sort` | `epc`, `commission`, `newest`, `name` | Standard: `epc` (netværkets indtjening pr. klik). |
| `limit` | 1–50 | Standard 20. |
| `cursor` | tekst | Fra `next_cursor`. |
Hvert program har bl.a. `program_id`, `store`, `joined`, `commission` (`type`, `value`, `text`), `cookie_days`, `approval_days`, `recurring_months`, `allowed_channels`, `paid_search` (`allowed`, `no_brand` eller `forbidden`), `allow_coupon_sites`, `network` (EPC, konvertering, ordreværdi og godkendelsesrate – afviste salg er ikke med) og `join_url`. Tilmelding sker i dashboardet via `join_url` – API'et tilmelder dig ikke.
### GET /programs
De programmer, du er tilmeldt, med provision, cookie-varighed, kanaler, de gemte regler og butikkens retningslinjer (forkortet). `active: false` betyder, at nye salg ikke giver provision; `inactive_reason` siger hvorfor (`program_paused` eller `store_inactive`). Feltet `brand_bidding` er forældet – brug `paid_search`. Parameter: `search` (butiksnavn).
### GET /programs/{id}
Alt om et program: butikkens beskrivelse og målgruppe, retningslinjerne ordret, reglerne i dag (`rules.now`), de gemte regler (`rules.saved`) og datoen, hvor en stramning træder i kraft (`rules.tightening_effective_from`), en `compliance_summary`, regelvagtens tilstand (`enforcement.mode`: `shadow` eller `enforce`), rabatkoder, materialer (bannere med færdig HTML og dit link, når du er tilmeldt), topprodukter, netværkstal og dine egne tal for programmet (`mine`).
### GET eller POST /programs/{id}/compliance
Tjekker en planlagt promovering mod programmets målbare regler. Send mindst en af parametrene. Brug POST med JSON, når `content` er lang.
| Parameter | Beskrivelse |
|---|---|
| `channel` | Planlagt kanal, fx `Instagram`, `SEO`, `E-mail`. |
| `placement_url` | Hele adressen på siden, hvor linket skal stå. |
| `landing_page` | Sti eller adresse hos butikken. |
| `ad_type` | `paid_search_brand`, `paid_search_generic`, `coupon_site`, `cashback_site`, `social_post`, `newsletter` eller `website_content`. |
| `content` | Udkast til teksten (højst 20.000 tegn) – tjekkes for reklamemarkering. |
Svaret har `verdict` (`ok`, `warning` eller `not_allowed`), `checks` med en dom og en begrundelse pr. regel (og `effective_from`, når en stramning er på vej), butikkens retningslinjer ordret og regelvagtens tilstand. Kun de målbare regler dømmes: kanal, rabatkode- og cashbacksider ud fra placeringens domæne, betalt søgning som angivet og landingssiden. Reklamemarkeringen er et heuristisk tjek, og butikkens fritekst vurderes ikke. Se [Programregler](https://www.affilyflow.com/docs/affiliate/regler).
```bash title="Må jeg køre Google Ads på generiske søgeord?"
curl "https://app.affilyflow.com/api/v1/programs/PROGRAM_ID/compliance?ad_type=paid_search_generic" \
-H "Authorization: Bearer $AFFILYFLOW_API_KEY"
```
## Produkter
### GET /products
Søger i butikkernes varer på tværs af åbne programmer.
| Parameter | Type | Beskrivelse |
|---|---|---|
| `query` | tekst | Søgeord, fx `sofa grå`. Tom = de varer, du tjener mest på. |
| `program_id` | UUID | Kun et program. |
| `min_commission_pct` | tal 0–100 | Mindste provision i procent. |
| `in_stock_only` | `true` / `false` | Skjul udsolgte varer. |
| `sort` | `earnings`, `relevance`, `price`, `rate` | Standard: `relevance` med søgeord, ellers `earnings`. |
| `limit` | 1–60 | Standard 20. |
| `cursor` | tekst | Fra `next_cursor`. |
Hver vare har `product_id`, `title`, `url`, `image_url`, `price`, `currency`, `in_stock`, `store`, `program_id`, `commission`, `est_commission` og `joined`. For tilmeldte programmer er `tracking_url` dit eksisterende link sendt direkte til varen (`?p=`). `est_commission` er et skøn: satsen af prisen uden moms eller det faste beløb.
## Sites
### GET /websites
Dine registrerede sites med `website_id`, `domain`, `name`, `niche`, `monthly_traffic` og `created_at`.
### POST /websites
Tilføjer et site med de samme regler som **Mine sites**.
| Felt | Beskrivelse |
|---|---|
| `domain` | Påkrævet. Fx `minside.dk`. En hel adresse accepteres og skæres ned til domænet. |
| `name` | Visningsnavn, højst 120 tegn. Standard: domænet. |
| `niche` | Fx `Bolig`, højst 80 tegn. |
| `monthly_traffic` | Omtrentlige besøgende pr. måned. |
Et nyt site svarer HTTP 201. Har du allerede domænet, oprettes intet, og svaret er det eksisterende site med `already_existed: true` og HTTP 200. Svaret har `history` med de klik, salg og den provision, der allerede er kommet fra domænet.
### PATCH /websites/{id}
Retter `domain`, `name`, `niche` eller `monthly_traffic`. Send `null` eller `""` for at rydde `name`, `niche` eller `monthly_traffic`. Et domæne, du allerede har på et andet site, afvises med `duplicate_domain`. Sites kan kun slettes i dashboardet.
### GET /sites
Sites målt på klikkene – registrerede og opdagede – med klik, salg, provision, EPC, side-sporingens dækning og metode, topbutikker og topsider. `suggest_add: true` markerer et opdaget site med mindst 3 klik eller et salg, som du ikke har tilføjet.
| Parameter | Beskrivelse |
|---|---|
| `from`, `to` | Valgfri periode. Uden: hele historikken. |
| `pages_per_site` | 0–25 topsider pr. site. Standard 5. |
| `include_minor` | Medtag opdagede sites med meget få klik. |
### GET /pages
Siderne på dine sites, sorteret efter provision, med klik, salg, EPC, konvertering, bedst sælgende vare, butikker og udvikling over 30 dage. Klik uden kendt side står som en række pr. site med `known: false`.
| Parameter | Beskrivelse |
|---|---|
| `from`, `to` | Valgfri periode. Uden: hele historikken. |
| `site` | Kun sider på dette site (domæne uden www). |
| `known_only` | Skjul rækkerne uden kendt side. |
| `min_clicks` | Mindste antal klik. |
| `limit` | 1–200. Standard 50. |
| `cursor` | Fra `next_cursor`. |
## Links
### GET /links
Dine links, nyeste først.
| Parameter | Beskrivelse |
|---|---|
| `program_id` | Kun links til dette program. |
| `sub_id` | Kun links med dette sub-ID. |
| `search` | Tekst i navn, butik, destination eller sub-ID. |
| `archived` | `exclude` (standard), `include` eller `only`. |
| `include_stats` | `true` tilføjer klik, salg, provision, EPC og konvertering pr. link. |
| `limit` | 1–200. Standard 50. |
| `cursor` | Fra `next_cursor`. |
Hvert link har `link_id`, `url`, `program_id`, `store`, `active`, `name`, `landing_page`, `destination`, `sub_id`, `channel`, `utm`, `website_id`, `placement_url`, `archived`, `created_at` og `html`.
### POST /links
Opretter et link – præcis som link-generatoren i dashboardet. Du skal være tilmeldt programmet.
| Felt | Beskrivelse |
|---|---|
| `program_id` | Påkrævet. Fra `/catalog` eller `/programs`. |
| `landing_page` | Sti eller hel adresse på butikkens domæne, fx `/produkter/sofa`. Standard: forsiden. |
| `sub_id` | Højst 80 tegn. |
| `campaign` | Højst 80 tegn. Sendes som `utm_campaign` og bliver linkets navn. |
| `channel` | En af programmets tilladte kanaler: `SEO`, `Blog`, `Instagram`, `TikTok`, `YouTube`, `Facebook`, `Pinterest`, `E-mail`, `Website`. |
| `utm_content`, `utm_term` | Højst 100 tegn hver. |
| `website_id` | Valgfri site-etiket fra `/websites`. |
| `placement_url` | Hele adressen på siden, hvor linket skal stå (`https://…`). |
| `name` | Linkets navn i dashboardet, højst 120 tegn. |
| `include_qr` | `true` tilføjer en QR-kode som PNG i `qr_png_base64`. |
```bash title="Opret et link"
curl -X POST https://app.affilyflow.com/api/v1/links \
-H "Authorization: Bearer $AFFILYFLOW_API_KEY" \
-H "Content-Type: application/json" \
-d '{"program_id":"PROGRAM_ID","landing_page":"/produkter/sofa","sub_id":"blog-bedste-sofaer-okt"}'
```
Et nyt link svarer HTTP 201. Findes der et identisk link, genbruges det med `reused: true` og HTTP 200.
```json title="Svar (forkortet)"
{
"link": {
"url": "https://app.affilyflow.com/go/3f9a1c07be",
"short_code": "3f9a1c07be",
"reused": false,
"program_id": "PROGRAM_ID",
"store": "Butik",
"landing_page": "/produkter/sofa",
"sub_id": "blog-bedste-sofaer-okt",
"html": "Butik",
"variants": {
"with_sub_id": "https://app.affilyflow.com/go/3f9a1c07be?sid={sub_id}",
"deeplink": "https://app.affilyflow.com/go/3f9a1c07be?p={path_on_store}"
},
"warnings": []
},
"warnings": [],
"quota": {
"links_created_last_24h": 1,
"links_remaining_24h": 499,
"daily_link_limit": 500,
"requests_per_minute": 120,
"requests_used_this_minute": 1,
"write_calls_per_minute": 60
}
}
```
`warnings` har regel-advarsler for programmet – fx en kanal eller placering, der ikke er tilladt, eller en stramning på vej. Brug `html` på dit site: så får hvert klik den præcise side med. Se [Side-sporing](https://www.affilyflow.com/docs/affiliate/side-sporing).
### POST /links/batch
Op til 50 links i et kald. Felterne på topniveau (`program_id`, `sub_id`, `campaign`, `channel`, `website_id`, `utm_content`, `utm_term`) er standardværdier, som hvert element i `items` kan overskrive. Et element kan desuden have `landing_page`, `placement_url` og `name`.
```bash title="Links til tre varer"
curl -X POST https://app.affilyflow.com/api/v1/links/batch \
-H "Authorization: Bearer $AFFILYFLOW_API_KEY" \
-H "Content-Type: application/json" \
-d '{"program_id":"PROGRAM_ID","sub_id":"artikel-sofaer-okt","items":[{"landing_page":"/produkter/sofa-a"},{"landing_page":"/produkter/sofa-b"},{"landing_page":"/produkter/sofa-c"}]}'
```
Svaret har et resultat pr. element (`index`, `ok` og enten `link` eller `error` med `code` og `message`), en `summary` (`requested`, `created`, `reused`, `failed`) og den resterende kvote. Et element, der fejler, stopper ikke de andre.
### GET /links/{id}
Et link med tal, `performs_on` (de sider, sites eller kanaler klikkene kom fra), programmets status og de regler, der gælder i dag, og `variants`. `?include_qr=true` tilføjer en QR-kode.
### PATCH /links/{id}
Retter `name`, `landing_page` (på butikkens domæne) eller `website_id` (`null` fjerner etiketten). Adressen ændrer sig aldrig.
Sub-ID, kampagne, kanal, program og UTM-værdier kan ikke ændres på et eksisterende link, fordi klikkene er registreret med dem. Forsøger du, svarer API'et HTTP 400 `immutable_field`. Lav et nyt link, eller sæt `?sid=` på det eksisterende.
### POST og DELETE /links/{id}/archive
POST arkiverer linket, DELETE henter det frem igen. Et arkiveret link virker stadig – det er kun skjult i listen.
## Resultater
### GET /stats
Klik, salg og provision for en periode.
| Parameter | Beskrivelse |
|---|---|
| `from`, `to` | Periode. Standard: de seneste 30 dage. |
| `group_by` | `total` (standard), `sub_id`, `program`, `link`, `day`, `site`, `page`, `source`, `device` eller `country`. |
| `program_id` | Kun dette program. |
| `sub_id` | Kun dette sub-ID. |
| `compare` | `true` tilføjer perioden før (samme længde) med ændring i procent. |
| `limit` | 1–500 rækker ved gruppering. Standard 100. `day` giver altid alle dage. |
`totals` og hver række i `rows` har `clicks`, `sales`, `sales_pending`, `sales_approved`, `sales_rejected`, `revenue`, `commission`, `commission_pending`, `commission_approved`, `conversion_rate_pct` og `epc`. Rækker har desuden `key` og `label`. Et salg tælles på den dag, det blev registreret. Ved `site`, `page`, `source`, `device` og `country` grupperes salget efter det klik, det kom fra.
```bash title="Sidste måned pr. program med sammenligning"
curl "https://app.affilyflow.com/api/v1/stats?from=2026-09-01&to=2026-09-30&group_by=program&compare=true" \
-H "Authorization: Bearer $AFFILYFLOW_API_KEY"
```
### GET /sales
Enkelte salg, nyeste først.
| Parameter | Beskrivelse |
|---|---|
| `from`, `to` | Periode for salgets dato. Standard: de seneste 30 dage. |
| `status` | `pending`, `approved` eller `rejected`. |
| `program_id` | Kun dette program. |
| `limit` | 1–200. Standard 50. |
| `cursor` | Fra `next_cursor`. |
Hvert salg har `sale_id`, `created_at`, `store`, `order_ref` (kun de sidste tegn af ordrenummeret), `order_value`, `commission_base` (når provisionen er regnet af et andet beløb end ordren), `commission` (efter eventuelle delvise refusioner), `commission_before_refund`, `refunded_value`, originalvaluta og kurs, `status` og `status_text`, `rejection_reason` (fx *Regelbrud: …*), `approval_due_at` for afventende salg, `items`, og klikket: `click_id`, `clicked_at`, `link_id`, `sub_id` og `landing_page`.
### GET /clicks
Enkelte klik med hele sporet: tidspunkt, siden linket stod på og hvordan den er kendt (`page_basis`), site, kilde med grundlag, link og sub-ID, landingsside, enhed, land, den låste provision og de salg, klikket førte til. Perioden følger klikkets dato.
| Parameter | Beskrivelse |
|---|---|
| `from`, `to` | Periode. Standard: de seneste 30 dage. |
| `site` | Kun klik fra dette site (domæne uden www). |
| `page` | Kun klik fra sider, der starter med denne adresse – eller `unknown` for klik uden kendt side. |
| `link_id`, `program_id`, `sub_id` | Filtre. |
| `source` | Kun denne kilde, fx `Google`, `Instagram`, `Direkte`. |
| `outcome` | `sale`, `no_sale` eller `rejected` (kun afviste salg). |
| `limit` | 1–200. Standard 50. |
| `cursor` | Fra `next_cursor`. |
## Saldo og udbetalinger
Kun læsning. Svarene indeholder aldrig IBAN (heller ikke maskeret), CPR, TIN eller adresse, og API'et kan ikke bede om udbetaling.
### GET /balance
`balance` (til rådighed, afventende salg, undervejs, optjent i alt og udbetalt – alt uden moms), `payout` (næste automatiske udbetalingsdag, minimum, om du kan bede om udbetaling nu, og hvad der mangler – med kode, tekst og `you_can_fix`) og de 5 seneste udbetalinger. Se [Udbetaling](https://www.affilyflow.com/docs/affiliate/udbetaling).
### GET /payouts
Alle udbetalinger, nyeste først: status, automatisk eller anmodet, beløb uden moms, moms, overført beløb, datoer og afregningsbilagets nummer. Parametre: `limit` (1–100, standard 24) og `cursor`.
## Fejlkoder
| HTTP | Kode | Betyder |
|---|---|---|
| 400 | `invalid_input` | En parameter mangler eller er ugyldig. Beskeden siger hvilken. |
| 400 | `invalid_json` | Body er ikke gyldig JSON. |
| 400 | `invalid_cursor` | `cursor` er ugyldig. Start forfra uden. |
| 400 | `channel_not_allowed` | Kanalen er ikke tilladt i programmet. Beskeden lister de tilladte. |
| 400 | `landing_page_other_host` | Landingssiden ligger ikke på butikkens domæne. |
| 400 | `invalid_landing_page` | Landingssiden er ikke en gyldig sti eller adresse. |
| 400 | `unknown_website` | `website_id` findes ikke på din konto. |
| 400 | `immutable_field` | Sub-ID, kampagne, kanal, program eller UTM kan ikke ændres på et eksisterende link. |
| 400 | `invalid_domain` | Domænet skal skrives som `minside.dk`. |
| 401 | `missing_key` | Ingen nøgle sendt. |
| 401 | `invalid_key` | Nøglen er ugyldig eller tilbagekaldt. |
| 403 | `not_joined` | Du er ikke tilmeldt programmet. Beskeden har linket til tilmelding i dashboardet. |
| 403 | `inactive` | Programmet eller butikken er ikke aktiv lige nu. |
| 403 | `account_suspended` | Kontoen er suspenderet. |
| 404 | `not_found` | Programmet, linket eller sitet findes ikke på din konto. |
| 409 | `duplicate_domain` | Du har allerede et site med det domæne. |
| 429 | `rate_limited` | For mange kald. Vent, og prøv igen. |
| 429 | `daily_link_limit` | 500 nye links det seneste døgn. Genbrug et link med `?sid=` eller `?p=`. |
| 500 | `server_error`, `save_failed` | Noget gik galt hos os. Prøv igen. |
| 503 | `unavailable` | API'et er midlertidigt utilgængeligt. |
Spørger du efter et link eller site, der tilhører en anden affiliate, svarer API'et `not_found` – det afslører ikke, at det findes.
## Det kan API'et ikke
- Tilmelde dig programmer – det sker i dashboardet, hvor butikken får besked.
- Slette links eller sites – links kan arkiveres, sites slettes kun i dashboardet.
- Se eller ændre bankoplysninger, stamdata eller profil – eller bede om udbetaling.
Kilde: https://www.affilyflow.com/docs/affiliate/rest-api
---
# MCP-server
> Forbind Claude, ChatGPT, Claude Code eller Cursor til din Affilyflow-konto – opsætning, login, de 23 værktøjer, prompts og grænser.
MCP-serveren lader din AI-assistent arbejde direkte med din Affilyflow-konto: finde programmer, læse butikkens regler, søge i varer, lave sporbare links og hente dine klik, salg, sider og saldo – i samtalen. Værktøjerne bruger de samme funktioner som dashboardet og [REST-API'et](https://www.affilyflow.com/docs/affiliate/rest-api), så tallene er de samme.
MCP-serveren er kun for affiliates.
## Adressen
```text title="MCP-adresse"
https://app.affilyflow.com/api/mcp
```
Adressen er til en MCP-klient, der sender POST-forespørgsler. Åbner du den i en browser, får du 405 Method Not Allowed – det er forventet. Indsæt adressen som connector i din assistent.
Før du starter: du skal have en affiliate-konto, og du skal være tilmeldt de programmer, du vil lave links til. Assistenten kan ikke tilmelde dig.
## Forbind Claude
1. Åbn **Customize → Connectors** i Claude, og vælg **Add custom connector**.
2. Giv forbindelsen navnet Affilyflow, og indsæt MCP-adressen.
3. Vælg **Connect**. Log ind på din Affilyflow-konto, og godkend forbindelsen.
4. Aktiver Affilyflow under Connectors i samtalen. Bed assistenten vise dine tilmeldte programmer.
På en teamkonto kan en ejer først skulle tilføje forbindelsen til organisationen. Se Anthropics vejledning: [Get started with custom connectors](https://support.claude.com/en/articles/11175166-get-started-with-custom-connectors-using-remote-mcp).
## Forbind ChatGPT
1. Åbn ChatGPT på web. Find **Developer mode** under Settings → Apps → Advanced Settings, hvis funktionen er tilgængelig for din konto.
2. Opret en custom app med navnet Affilyflow. Indsæt MCP-adressen, og vælg OAuth som godkendelse.
3. Log ind på Affilyflow, og godkend forbindelsen. På en arbejdskonto kan din administrator skulle give adgang først.
4. Vælg Affilyflow i samtalen. Bed ChatGPT vise de programmer, du er tilmeldt.
> [!NOTE]
> ChatGPT forbinder via Developer mode. Adgang og mulighed for at oprette links afhænger af abonnement, arbejdsområde og OpenAIs aktuelle funktioner. Se OpenAIs vejledning: [Developer mode and MCP apps in ChatGPT](https://help.openai.com/en/articles/12584461-developer-mode-and-mcp-apps-in-chatgpt).
## Forbind Claude Code
Kør kommandoen i din terminal:
```bash title="Terminal"
claude mcp add --transport http affilyflow https://app.affilyflow.com/api/mcp
```
Åbn derefter Claude Code, skriv `/mcp`, vælg Affilyflow, og gennemfør login i browseren. Du behøver ikke en API-nøgle. Fordi Claude Code kører på din egen computer, viser godkendelsessiden en advarsel – tillad kun, hvis du selv lige har startet forbindelsen.
## Forbind Cursor og andre MCP-klienter
Klienter, der ikke kan logge ind med OAuth, bruger en API-nøgle. Opret den under **AI og API** i dashboardet (se [REST-API](https://www.affilyflow.com/docs/affiliate/rest-api#kom-i-gang)), og sæt den ind i klientens konfiguration:
```json title="mcp.json"
{
"mcpServers": {
"affilyflow": {
"url": "https://app.affilyflow.com/api/mcp",
"headers": { "Authorization": "Bearer afk_DIN_NØGLE" }
}
}
}
```
## Login og adgang
Claude, ChatGPT og Claude Code forbinder med login i stedet for en nøgle (OAuth 2.1 med PKCE). Assistenten sender dig til Affilyflows egen login- og godkendelsesside. Den viser appens navn og den adresse, du sendes tilbage til, så du kan se, at det er den rigtige app. Assistenten får en adgang til MCP-serveren – den får aldrig din adgangskode.
| Assistenten kan | Assistenten kan ikke |
|---|---|
| Læse programmer, regler, produkter, dine links, sites, klik, salg, saldo og udbetalinger | Tilmelde dig programmer |
| Oprette, omdøbe og arkivere links | Slette links eller sites |
| Tilføje og rette dine sites | Se eller ændre bank-, CPR-, skatte- eller adresseoplysninger eller din profil |
| | Bede om udbetaling |
- En adgang gælder en time og fornyes automatisk. Fornyelsen gælder 60 dage og skiftes ved hver brug.
- Adgangen fra et login virker kun på MCP-serveren – ikke på REST-API'et.
- Under **AI og API** ser du dine forbundne apps og kan afbryde dem. API-nøgler tilbagekaldes samme sted. En afbrudt forbindelse virker ikke fra næste kald.
De data, du henter gennem assistenten, indgår hos den AI-tjeneste, du bruger. Vælg den konto og det arbejdsområde, du vil bruge til dine affiliateresultater.
## Værktøjerne
Serveren har 23 værktøjer. De læsende ændrer intet. De skrivende laver, retter eller arkiverer kun dine egne links og sites – intet værktøj sletter.
### Programmer og regler
| Værktøj | Gør |
|---|---|
| `search_programs` | Søger i alle åbne programmer – også dem, du ikke er tilmeldt – med provision, cookie, godkendelsesperiode, kanaler, regler, netværkets EPC og tilmeldingslink. |
| `list_programs` | De programmer, du er tilmeldt, med de gemte regler og butikkens retningslinjer. |
| `get_program` | Et program i dybden: beskrivelse, retningslinjer ordret, regler i dag og kommende stramninger med dato, rabatkoder, materialer med færdig banner-HTML, topprodukter, netværkstal og dine egne tal. |
| `check_compliance` | Tjekker en planlagt promovering mod programmets målbare regler: kanal, rabatkode- og cashbacksider, betalt søgning, landingsside og reklamemarkering. Svarer ok, advarsel eller ikke tilladt pr. regel. |
### Produkter og links
| Værktøj | Gør |
|---|---|
| `search_products` | Søger i butikkernes varer med pris, lager, anslået provision og – for tilmeldte programmer – et færdigt link til varen. |
| `create_link` | Opretter eller genbruger et link med landingsside, sub-ID, kampagne, kanal, UTM, navn, HTML-kode og QR-kode. Skrivende. |
| `create_links_batch` | Op til 50 links i et kald med et resultat pr. link. Skrivende. |
| `list_links` | Dine links, nyeste først, eventuelt med tal pr. link. |
| `get_link` | Et link med tal, de sider det virker på, programmets status og reglerne i dag. |
| `update_link` | Omdøber et link, skifter landingsside eller site-etiket. Skrivende. |
| `archive_link` | Skjuler et link fra listen – det virker stadig. Skrivende. |
| `restore_link` | Viser et arkiveret link igen. Skrivende. |
### Sites
| Værktøj | Gør |
|---|---|
| `get_sites` | Sites målt på klikkene med indtjening, side-sporingens dækning og forslag til sites, du kan tilføje (`suggest_add`). |
| `get_pages` | Siderne på dine sites med klik, salg, provision og udvikling. |
| `list_websites` | Dine registrerede sites. |
| `add_site` | Tilføjer et site. Tallene fra de klik, der allerede er kommet, følger med. Skrivende. |
| `update_site` | Retter domæne, navn, niche eller trafik – fx et domæne uden .dk. Skrivende. |
### Resultater
| Værktøj | Gør |
|---|---|
| `get_stats` | Klik, salg og provision for en periode – samlet eller pr. sub-ID, program, link, dag, site, side, kilde, enhed eller land, eventuelt sammenlignet med perioden før. |
| `list_sales` | Enkelte salg med status, afvisningsårsag, godkendelsesdato, refusioner og klikket. |
| `list_clicks` | Enkelte klik med side, kilde, enhed, land og de salg, klikket førte til. |
### Konto og udbetaling
| Værktøj | Gør |
|---|---|
| `get_account` | Din konto, kvote for nye links og genveje til dashboardet. |
| `get_balance` | Saldo, næste udbetalingsdag og hvad der mangler. Kun læsning. |
| `list_payouts` | Dine udbetalinger med afregningsbilag. Kun læsning. |
Parametre og svar er de samme som i [REST-API'et](https://www.affilyflow.com/docs/affiliate/rest-api). Hvert værktøj svarer med struktureret data og den samme JSON som tekst. `create_link` og `get_link` med `include_qr` sender QR-koden som et billede.
## Prompts
I Claude og Claude Code (og andre klienter, der understøtter prompts) findes fire færdige arbejdsgange:
| Prompt | Gør | Argumenter |
|---|---|---|
| `monthly_report` | Månedsrapport med hovedtal, bedste programmer og sider, afviste salg og næste udbetaling. | `month` (ÅÅÅÅ-MM, standard: sidste måned) |
| `find_programs_for_my_niche` | Finder de bedste åbne programmer til din niche og forklarer reglerne. | `niche`, `channel` |
| `optimize_links` | Gennemgår links, sider og side-sporing og foreslår forbedringer. | – |
| `build_product_links` | Finder varer og laver links til dem i et kald. | `query` (påkrævet), `store`, `sub_id`, `count` (standard 10, højst 50) |
ChatGPT bruger ikke prompts – værktøjerne virker uden.
## Hvad assistenten får at vide
Serveren giver assistenten en kort vejledning med: hvordan Affilyflow virker, en typisk arbejdsgang, at butikkens regler og retningslinjer skal følges, at en rabatkode ikke giver provision alene, at reklame skal markeres efter markedsføringsloven, at beløb er i kroner uden moms, og at datoer er i dansk tid. Assistenten afgør selv, hvordan den bruger det – tjek vigtige tal i dashboardet.
## Prøv det
- "Find de 10 sofaer, jeg tjener mest på hos Nordlys Bolig, og lav links til dem med sub-ID artikel-sofaer-okt. Tjek programmets regler først, og giv mig HTML-koden til mit site."
- "Jeg vil lave en TikTok-video og køre Google Ads på generiske søgeord for et af mine programmer. Må jeg det? Vis butikkens retningslinjer, og sig hvis reglerne ændrer sig snart."
- "Lav en rapport for sidste måned sammenlignet med måneden før: bedste programmer, sider og kilder, afviste salg med årsag og hvornår jeg får min næste udbetaling."
- "Tilføj de sites, der sender mig klik."
## Grænser
| Grænse | Værdi |
|---|---|
| Kald pr. minut | 120 pr. API-nøgle eller forbindelse |
| Skrivende kald pr. minut | 60 pr. API-nøgle eller forbindelse |
| Nye links | 500 pr. døgn pr. konto. Et genbrugt identisk link tæller ikke. |
| Links pr. batch | 50 |
## Fejl
Et værktøj, der fejler, svarer med en fejl og en kode, som assistenten kan reagere på, fx:
| Kode | Betyder |
|---|---|
| `not_joined` | Du er ikke tilmeldt programmet. Tilmeld dig i dashboardet, og prøv igen. |
| `channel_not_allowed` | Kanalen er ikke tilladt i programmet. |
| `landing_page_other_host` | Landingssiden ligger ikke på butikkens domæne. |
| `immutable_field` | Sub-ID, kampagne og kanal kan ikke ændres på et eksisterende link. Lav et nyt, eller brug `?sid=`. |
| `daily_link_limit` | 500 nye links det seneste døgn. |
| `invalid_domain`, `duplicate_domain` | Domænet er ugyldigt eller findes allerede på et af dine sites. |
| `rate_limited` | For mange kald. Vent lidt. |
Den fulde liste står under [REST-API → Fejlkoder](https://www.affilyflow.com/docs/affiliate/rest-api#fejlkoder).
Får du 401, er adgangen udløbet eller afbrudt: forbind assistenten igen, eller tjek at API-nøglen ikke er tilbagekaldt.
## Til dig, der bygger en klient
- **Transport:** Streamable HTTP uden sessioner. Hver POST er selvstændig, og der er ingen SSE-strøm på GET.
- **Protokol:** serveren taler både `2025-11-25`, `2025-06-18`, `2025-03-26` og `2024-11-05` (med `initialize`) og den statsløse `2026-07-28` (med `server/discover`, version og klientkapabiliteter i `params._meta` og headerne `MCP-Protocol-Version`, `Mcp-Method` og `Mcp-Name`).
- **Adgang:** `Authorization: Bearer afk_…` (API-nøgle) eller et OAuth-adgangstoken. Uden gyldig adgang svarer serveren 401 med `WWW-Authenticate: Bearer resource_metadata="…"`, som starter login i klienten.
OAuth-adresser (Affilyflow er selv autorisationsserver):
| Adresse | Formål |
|---|---|
| `/.well-known/oauth-protected-resource/api/mcp` | Ressourcens metadata (RFC 9728). |
| `/.well-known/oauth-authorization-server` | Autorisationsserverens metadata (RFC 8414). |
| `/oauth/authorize` | Login og godkendelse. |
| `/oauth/token` | Udveksling af kode og fornyelse (`authorization_code`, `refresh_token`). |
| `/oauth/register` | Dynamisk klientregistrering. |
| `/oauth/revoke` | Tilbagekald et token. |
PKCE med `S256` er påkrævet, scope er `affiliate` (plus `offline_access` for fornyelse), og tokenet bindes til ressourcen `https://app.affilyflow.com/api/mcp`. Klienter kan identificere sig med et Client ID Metadata Document (en URL som `client_id`) eller med dynamisk registrering. Redirect-adresser skal være https eller loopback.
```bash title="Kald et værktøj med en API-nøgle"
curl -X POST https://app.affilyflow.com/api/mcp \
-H "Authorization: Bearer $AFFILYFLOW_API_KEY" \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"get_stats","arguments":{"group_by":"program","compare":true}}}'
```
Kilde: https://www.affilyflow.com/docs/affiliate/mcp
---
# Affilyflow developer docs
> Technical reference for tracking sales with Affilyflow and the affiliate API – for developers and AI coding assistants.
## What's here
The English docs cover the technical reference. Store setup guides for each platform are in [Danish](https://www.affilyflow.com/docs).
- [track.js](https://www.affilyflow.com/docs/en/reference/track-js) – the tracking script: attributes, JavaScript API, cookies and consent.
- [Sale line](https://www.affilyflow.com/docs/en/reference/sale-line) – report the sale from the order confirmation page with one line of HTML.
- [Google Analytics](https://www.affilyflow.com/docs/en/reference/google-analytics) – read the purchase from the site's GA4 event, no field names needed.
- [Conversion API](https://www.affilyflow.com/docs/en/reference/conversion-api) – report sales from your own server.
- [Status codes](https://www.affilyflow.com/docs/en/reference/status-codes) – what each response means and what to do.
- [REST API](https://www.affilyflow.com/docs/en/affiliate/rest-api) and [MCP](https://www.affilyflow.com/docs/en/affiliate/mcp) – for affiliates and their AI assistants.
## How it fits together
1. An affiliate shares a link. The click goes through Affilyflow and lands on the store with a click id (`?afid=c_…`).
2. The store's tracking remembers the click (the Shopify app's Web Pixel, the WooCommerce plugin or `track.js`).
3. The customer buys. The sale is reported with order number and amount and linked to the click.
4. The commission is locked on the click and approved once the approval period has passed, unless the store rejects the sale.
> [!TIP]
> Every page is also available as Markdown (add `.md` to the address), and the whole documentation is in `/llms-full.txt` for your AI coding assistant.
Kilde: https://www.affilyflow.com/docs/en
---
# track.js
> Complete reference for the tracking script – attributes, JavaScript API, what is stored in the browser, consent and errors.
track.js is Affilyflow's tracking script for webshops and websites. It stores the click when a customer lands from an affiliate link and reports the sale when the customer reaches the receipt.
## Add the script
Add the line to `` on every page – including the receipt page.
```html title=" · every page"
```
`DIN_BUTIK` is your store ID. The exact line with your ID is under Setup in the store dashboard – copy it from there.
Use `async`: the script must never hold the page back. The exception is the no-code lead trigger (`data-lead-url`), where the line must be without `async` – see [Leads without code](#leads-without-code).
## Attributes
| Attribute | Required | Meaning |
|---|---|---|
| `data-store` | Yes | Your store ID. Sent with every sale and must match the click's store, otherwise the sale is rejected as `store_mismatch`. |
| `data-consent="required"` | No | Nothing is stored in the browser until the visitor has consented to marketing. See [Consent](#consent). |
| `data-lead-url` | No | The thank-you page of a lead program. When the page matches, a lead is reported automatically. See [Leads without code](#leads-without-code). |
| `data-cookie-domain` | No | The domain the cookie is set on (e.g. `shop.com`). Without it, the script picks the broadest domain the browser allows. |
| `data-sale="ga4"` | No | Read the purchase from Google Analytics 4 (dataLayer or gtag). See [Google Analytics](https://www.affilyflow.com/docs/en/reference/google-analytics). |
| `data-sale-vat` | No | Only with `data-sale="ga4"`: the VAT percentage included in the Google Analytics amounts (e.g. `25`). |
| `data-sale-shipping` | No | Only with `data-sale="ga4"`: `incl` if shipping is always included in `value`, `excl` if it never is. Without it, the script guesses. |
## What the script stores
When a customer lands with `?afid=` in the URL, the script stores the click ID both as a cookie and in localStorage, so it survives either one being cleared.
| Key | Content |
|---|---|
| `af_click` | The click ID from `?afid=` |
| `af_store` | The store ID from `data-store` |
| `af_aff` | Only for legacy direct links (`?aff=` + `?store=`) |
- **Lifetime:** at most 90 days from the click. The cookie's `max-age` is always what is left of the 90 days.
- **Return visits never extend the window.** On every visit the cookie and localStorage are written again with the remaining time, so a deleted cookie is restored from localStorage – but the timestamp is always the click's.
- **localStorage** stores the value as `{ v, t }` – the value and the time of the click. A value older than 90 days is deleted when read.
- **The cookie** is set with `path=/` and `SameSite=Lax`, and with `Secure` on https.
- **Domain:** the cookie is set on the broadest domain the browser allows (`shop.com` rather than `www.shop.com`), so the click follows the customer to a receipt on another subdomain. The script finds it with a short-lived test cookie (`_af_d`) that is deleted immediately. On an IP address or `localhost` the exact host is used.
- **Memory:** the click ID from the URL is also kept in the page's memory, so a sale on the same page works even when storage is blocked (e.g. a private window).
> [!NOTE]
> The server decides the real window. Each program has its own cookie window, locked on the click. If the click is older than the program's window, the sale is rejected as `expired` – even if the browser still has it.
### Only Affilyflow click IDs
Only an `afid` that looks like ours (`c_` followed by 4–64 characters: letters, digits, `_` and `-`) is stored. If the store also uses another network with a parameter called `afid`, the foreign value can never overwrite an Affilyflow click.
## affilyflow.conversion()
Report a sale from JavaScript. The call only works once track.js is loaded on the page.
```js title="Receipt page"
affilyflow.conversion({ order: '1008', total: 499, currency: 'DKK' });
```
| Field | Type | Meaning |
|---|---|---|
| `order` | text | The order number. Used to stop duplicates. |
| `total` | number or text | The amount commission is calculated on: goods after discount, excluding VAT and shipping. Text such as `"1.249,00"` is parsed, see [Amounts as text](#amounts-as-text). |
| `currency` | text | Currency code, e.g. `DKK` or `EUR`. Upper-cased. Default `DKK`. |
| `store` | text | The store ID. Only used if the browser doesn't already have it from `data-store`. |
How the call behaves:
- **Without an affiliate click in the browser nothing is sent** – and no error is logged. A normal purchase without an affiliate is not an error.
- The sale is sent with `navigator.sendBeacon`, so it arrives even if the page navigates away right after. Without sendBeacon an invisible image request is used.
- With `data-consent="required"` the call waits until consent is settled and is sent only then.
- For a lead program: `affilyflow.conversion({ order: 'lead_' + Date.now() })` – no amount.
> [!WARNING]
> With `async`, `window.affilyflow` may not be ready when inline code on the receipt runs. Wait for it, or use the [sale line](https://www.affilyflow.com/docs/en/reference/sale-line), which needs no JavaScript at all.
```html title="Receipt page · wait for track.js"
```
### Amounts as text
A number is used as is. Text that is a valid machine number is also used as is. Other text is cleaned: everything except digits, dot, comma and minus is removed, and the **last** separator is the decimal mark. If there is only one separator with exactly three digits after it, it is a thousands separator.
| Sent as `total` | Read as |
|---|---|
| `499` | 499 |
| `"1249.5"` | 1249.5 |
| `"249.125"` | 249.125 |
| `"1.249,00"` | 1249 |
| `"1.249,-"` | 1249 |
| `"499,00 kr."` | 499 |
| `"kr. 499"` | 499 |
| `"1,234.56"` | 1234.56 |
> [!WARNING]
> The text `"1.249"` is a valid machine number and `conversion()` reads it as 1.249. Send whole amounts as numbers (`1249`) or with decimals (`"1.249,00"`). The [sale line](https://www.affilyflow.com/docs/en/reference/sale-line), on the other hand, reads `"1.249"` as 1249, because the value comes from page text.
### Rejected placeholders
If the template's placeholder is still in `order`, the sale is sent **without** an order number – a placeholder must never take up an order number, or every later sale would be rejected as a duplicate. These values count as placeholders (case-insensitive):
- `DIN_ORDRE_ID`, `DIT_ORDRE_ID`, `ORDRE_ID`, `ORDER_ID`, `YOUR_ORDER_ID`, `ORDERID`
- `XX`, `XXX` etc. (two or more X)
- Anything in `{{ … }}`, `[ … ]`, `< … >` or `% … %` – a template variable that wasn't filled in
The server applies the same rule.
### Duplicate guard in the browser
The server never counts the same order number twice in the same program. The browser also remembers what it has sent, so a reloaded receipt doesn't send again:
- **With an order number:** kept in localStorage (`af_sent`) for 30 days.
- **Without an order number:** kept in sessionStorage (`af_sent_s`) as page and amount – the same receipt with the same amount is sent once per tab.
## affilyflow.consent()
The store's own consent solution can control the script directly. Only relevant with `data-consent="required"`.
```js title="After the visitor's choice"
affilyflow.consent(true); // store the click from the URL and send waiting sales
affilyflow.consent(false); // delete af_click, af_store, af_aff and af_sent
```
If the visitor says yes again on the same page, the click from the URL is stored again.
## Consent
With `data-consent="required"` the script stores nothing in the browser – no cookie, no localStorage or sessionStorage, no test cookie – until marketing consent is given. Until then the click only lives in the page's memory and is not used. Without the attribute the script behaves as before.
```html title=" · every page"
```
The script reads consent itself from:
| Source | What counts as yes |
|---|---|
| `affilyflow.consent(true)` | The call |
| `window.affilyflowConsent` | The value `true` |
| Shoporama's banner | `shoporamaConsent.isAllowed('marketing')` |
| Cookie Information | The category `cookie_cat_marketing` |
| Cookiebot | `Cookiebot.consent.marketing` |
| IAB TCF v2 | Publisher consent to purposes 1 and 7 (or GDPR doesn't apply) |
- A yes works whether the banner answers before or after the script. Cookie Information's, Cookiebot's and Shoporama's events are listened to throughout. `window.affilyflowConsent` is checked on load and for the first 10 seconds, and TCF is used when its API is present within those 10 seconds.
- A no only deletes on an active action (Cookie Information's and Cookiebot's events, TCF's `useractioncomplete` or `affilyflow.consent(false)`) – never because the banner hasn't read its own cookie yet.
- If you set `window.affilyflowConsent` later than 10 seconds after load, call `affilyflow.consent(true)` instead.
## Leads without code
For lead programs the script can report a lead by itself when the customer reaches the thank-you page. Put the address in `data-lead-url` and remove `async`, so the lead is sent before the thank-you page possibly redirects.
```html title=" · every page · lead program"
```
- `/thanks` matches `/thanks` and `/thanks/…`, but not `/thanksgiving`.
- A full URL (`https://shop.com/thanks`) is read as its path.
- If parameters are given (`/thanks?type=lead`), all of them must be in the page's URL.
- The front page alone (`/`) only matches when parameters are given too.
- The lead is reported once per session and only with an affiliate click in the browser.
> [!NOTE]
> A lead (an `order` starting with `lead_`) on a program that pays per sale is rejected as `stale_lead_trigger`. Remove the lead trigger when the program has switched to sales.
## Console errors
track.js only writes to the browser console when a sale from the [sale line](https://www.affilyflow.com/docs/en/reference/sale-line) or [Google Analytics](https://www.affilyflow.com/docs/en/reference/google-analytics) could not be reported:
| Message | Cause |
|---|---|
| `Affilyflow: data-affilyflow-order must be the order number – the sale was not recorded.` | The sale line's order number is missing or a placeholder. |
| `Affilyflow: data-affilyflow-total must be the amount (goods excl. VAT and shipping) – the sale was not recorded.` | The sale line's amount is empty or 0. |
| `Affilyflow: the purchase event has no amount – the sale was not recorded.` | The Google Analytics purchase had no amount. |
What each response from the server means is in [Status codes](https://www.affilyflow.com/docs/en/reference/status-codes).
## Safari and iPhone
Safari (ITP) deletes cookies and localStorage set from JavaScript after about 7 days without a visit to the site. track.js rewrites its storage on every visit, but cannot and should not get around the limits. Only a cookie set by the store's own server – such as the WooCommerce plugin – lives longer. This applies to every network, not just Affilyflow.
## Legacy direct links
Links with `?aff=` (or `?aff_id=`) and `?store=` instead of `?afid=` still work. The script counts the click and stores `af_aff` and `af_store`. `data-store` always wins over `?store=` in the URL, so a store that uses `?store=` itself doesn't get its sales rejected.
Kilde: https://www.affilyflow.com/docs/en/reference/track-js
---
# The sale line
> Report the sale with a single line of HTML on the receipt page – no JavaScript, and Danish amounts are parsed correctly.
The sale line is the simplest way to report a sale: a hidden element on the receipt page that [track.js](https://www.affilyflow.com/docs/en/reference/track-js) finds and reports. There is no JavaScript to write, so a template variable can't break the code.
## The line
```html title="Receipt page"
```
Replace `1008` and `499,00` with your template variables – the order number and the goods amount after discount, excluding VAT and shipping.
The line only works when track.js is on the same page. Add the script to every page, including the receipt.
## Attributes
| Attribute | Required | Value |
|---|---|---|
| `data-affilyflow-order` | Yes | The order number – or `?parameter` to read it from the page URL. |
| `data-affilyflow-total` | Yes | The amount – or `#id` to read the text of the element with that ID. |
| `data-affilyflow-vat` | No | The VAT percentage included in the amount (e.g. `25`). It is subtracted. |
| `data-affilyflow-currency` | No | Currency code, e.g. `EUR`. Default `DKK`. |
Use `hidden` so the element isn't shown. It makes no difference to tracking.
## Values from the page
If the site builder has no template variables (e.g. Webflow), the line can read the values from the page itself:
- `data-affilyflow-order="?orderId"` reads the `orderId` parameter in the URL (`/order-confirmation?orderId=WF-55` gives `WF-55`).
- `data-affilyflow-total="#af-total"` reads the text of the element with `id="af-total"`.
- `data-affilyflow-vat="25"` subtracts 25 % VAT when the amount on the page includes VAT.
```html title="Receipt page · Webflow"
```
With `1.249,00 kr.` in `#af-total`, 999.20 is reported (1,249 without 25 % VAT).
## How the amount is read
- Danish amounts are parsed: `499,00`, `1.249,00`, `1.249,-`, `499,00 kr.` and `kr. 499`.
- `1.249` is read as 1249 – in the line, a dot followed by three digits is always a thousands separator, because the value comes from page text.
- Machine numbers such as `1249.5` and `89.90` are used as is.
- With `data-affilyflow-vat` the amount is divided by 1 + VAT/100 and rounded to two decimals. The value must be above 0 and below 100.
The amount must be what commission is calculated on: goods after discount, excluding VAT and shipping.
## Timing
Many pages render the receipt after load (Livewire, React, Webflow). The line handles it:
- track.js looks for the element when the page has loaded and watches for new elements for up to 60 seconds.
- If the order number is missing or the amount has no digits yet, the script waits up to 20 seconds for them.
- Each element is reported once. It is marked with `data-affilyflow-sent="1"` when read.
- Once a sale is found, the script stops looking.
A reloaded receipt is not sent again: the browser remembers the order number for 30 days, and the server never counts the same order number twice in the same program.
## When nothing is sent
- **No affiliate click in the browser.** A normal purchase without an affiliate is not sent, and no error is logged. Test with the test link from the store dashboard.
- **No consent yet.** With `data-consent="required"` the sale waits until consent is settled. See [track.js](https://www.affilyflow.com/docs/en/reference/track-js#consent).
- **The order number is missing or a placeholder.** The line requires an order number. `ORDRE_ID`, `{{ $order->id }}`, `[OrderID]`, `<…>` and `%OrderID%` that weren't filled in are rejected.
- **The amount is empty or 0.**
The last two write an error to the browser console:
```text title="Browser console"
Affilyflow: data-affilyflow-order must be the order number – the sale was not recorded.
Affilyflow: data-affilyflow-total must be the amount (goods excl. VAT and shipping) – the sale was not recorded.
```
> [!TIP]
> The live test under Setup in the store dashboard shows the amount that arrived and asks whether it matches your test order. It catches both VAT and shipping that slipped in.
## The line or JavaScript?
The line and [`affilyflow.conversion()`](https://www.affilyflow.com/docs/en/reference/track-js#affilyflow-conversion) report the same sale. Use the line unless you already have a script that works – the old call works unchanged. Use only one of them on the same receipt, or send the same order number in both, so the duplicate guard stops the second.
Kilde: https://www.affilyflow.com/docs/en/reference/sale-line
---
# Google Analytics
> Let track.js read the purchase from Google Analytics 4 with data-sale="ga4" – no code on the receipt page.
If your site already sends the purchase to Google Analytics 4 – via Google Tag Manager (`dataLayer`) or `gtag` – [track.js](https://www.affilyflow.com/docs/en/reference/track-js) can read the order number and amount from there. Nobody needs to know the template's field names, and there is nothing to add to the receipt.
## Turn it on
Add `data-sale="ga4"` to the script on every page. The exact line with your settings is under Setup in the store dashboard.
```html title=" · every page"
```
| Attribute | Value |
|---|---|
| `data-sale="ga4"` | Read the purchase from Google Analytics 4. Without it, Google Analytics is ignored entirely. |
| `data-sale-vat` | The VAT percentage included in the Google Analytics amounts. `25` if the amounts include Danish VAT; leave it out if they exclude VAT. |
| `data-sale-shipping` | `incl` if shipping is always included in `value`, `excl` if it never is. Without it, the script guesses. |
> [!WARNING]
> Use only one way to report the sale. If the site has the WooCommerce plugin or the [sale line](https://www.affilyflow.com/docs/en/reference/sale-line) on the receipt, don't turn on `data-sale="ga4"` – the same purchase could be reported twice with different order numbers. That's why the Google Analytics route is only active with the attribute.
## Formats that are read
The `purchase` event is read in all three forms:
```js title="Google Tag Manager · dataLayer"
dataLayer.push({
event: 'purchase',
ecommerce: {
transaction_id: '1008',
value: 675,
shipping: 49,
currency: 'DKK',
items: [{ item_id: '317', price: 375, quantity: 2 }]
}
});
```
```js title="gtag"
gtag('event', 'purchase', { transaction_id: '1008', value: 1000, currency: 'DKK' });
```
A `purchase` event in `dataLayer` without an `ecommerce` object is read too – the fields are then taken directly from the event.
- Events already in `dataLayer` when the script loads are read immediately.
- Events pushed later are caught straight away – also when Tag Manager loads after track.js. In addition, `dataLayer` is checked every second for 60 seconds.
- Other events (`view_item`, `add_to_cart` …) are ignored.
- A purchase without `transaction_id` is ignored.
- Each order number is reported once per page; the browser's and server's duplicate guards stop the rest.
### Fields used
| Field | Used for |
|---|---|
| `transaction_id` | The order number. Required. |
| `value` | The order amount. |
| `shipping` | Shipping, subtracted when it is included in `value`. |
| `items[].price` and `items[].quantity` | The sum of the item lines (`quantity` is 1 if missing). Used when `value` is missing, and to guess whether shipping is included. |
| `currency` | The currency. If missing, the first item's `currency` is used, otherwise `DKK`. |
`tax` is not read. VAT is taken out with `data-sale-vat`.
## How the amount is calculated
1. The starting point is `value`. If it is 0 or missing, the item lines' sum is used.
2. Shipping is subtracted when `shipping` is above 0 and either `data-sale-shipping="incl"` is set, or (without `excl`) `value` is larger than the item lines' sum – then shipping is included in `value`.
3. With `data-sale-vat` it is divided by 1 + VAT/100.
4. The result is rounded to two decimals. If it isn't above 0, nothing is reported, and the console shows `Affilyflow: the purchase event has no amount – the sale was not recorded.`
| `value` | `shipping` | Item lines | Setting | Reported |
|---|---|---|---|---|
| 675 | 49 | 750 | VAT 25 | 540 |
| 799 | 49 | 750 | VAT 25 | 600 |
| 724 | 49 | 750 | VAT 25, shipping `incl` | 540 |
| 1000 | – | – | VAT 25 | 800 |
| 540 | – | – | no VAT | 540 |
> [!NOTE]
> A discount larger than the shipping can fool the guess: `value` ends up below the item lines even though shipping is included. Set `data-sale-shipping="incl"` (or choose it in Setup) if your `value` always includes shipping.
## The server recalculates
Besides the amount, the script sends Google Analytics' raw figures with the sale: `value`, `shipping` and the item lines' sum. If the store has saved its Google Analytics setup under Setup in the dashboard (VAT and shipping), the server recalculates the amount with it – using exactly the same rule as above.
- A change to VAT or shipping in Setup applies from the next sale. The script doesn't need to be redeployed.
- The server's amount is only used if it is above 0 and not above what Google Analytics itself reported. Otherwise the script's amount is used.
- Without a saved Google Analytics setup, the script's amount from the attributes is used.
- Only sales from the Google Analytics route are recalculated. Sales from the sale line, `affilyflow.conversion()` and the integrations are untouched.
The live test under Setup shows the amount that arrived, so you can see whether VAT and shipping are right.
## Consent and clicks
The Google Analytics route follows the same rules as the rest of track.js:
- Without an affiliate click in the browser nothing is sent.
- With `data-consent="required"` the sale waits until the visitor has consented to marketing. See [track.js](https://www.affilyflow.com/docs/en/reference/track-js#consent).
Kilde: https://www.affilyflow.com/docs/en/reference/google-analytics
---
# Conversion API
> The endpoint that receives sales – for server-side and custom integrations, with parameters, responses and limits.
Every sale from [track.js](https://www.affilyflow.com/docs/en/reference/track-js) ends up at the same endpoint. If you build the integration yourself – e.g. from your own backend once payment has gone through – you can call it directly.
```http title="Endpoint"
POST https://app.affilyflow.com/api/track/conversion
GET https://app.affilyflow.com/api/track/conversion
```
- Both `GET` and `POST` work. `OPTIONS` answers CORS preflight requests.
- CORS is open (`Access-Control-Allow-Origin: *`), so the endpoint can be called from any browser.
- No key is required. What ties the sale to an affiliate is the click ID.
## The click ID first
Without the click ID the sale can't be attributed to anyone. It arrives from the affiliate link as `?afid=` on your site, and track.js stores it in the `af_click` cookie on your domain. Your server can therefore read the cookie from the customer's request when the order is created and send it along.
If you don't have track.js on the site, store `afid` from the URL yourself. Only accept values that look like ours: `c_` followed by 4–64 characters (letters, digits, `_` and `-`).
> [!TIP]
> Save the click ID on the order when it is created. Then the sale can be reported from the server even when payment is confirmed later and the customer never sees the receipt.
## Parameters
| Parameter | Alias | Required | Meaning |
|---|---|---|---|
| `afid` | `click_id` | Yes | The click ID (`c_…`). |
| `store` | – | Recommended | Your store ID (the same as `data-store`). Must match the click's store, otherwise `store_mismatch`. |
| `order` | `order_id` | Recommended | The order number. The same number is never counted twice in the same program. At most 120 characters. |
| `total` | `order_total` | Yes | The amount commission is calculated on: goods after discount, excluding VAT and shipping. A machine number (`1249.5`). |
| `currency` | – | No | ISO 4217 currency code, e.g. `DKK` or `EUR`. Default `DKK`. |
> [!WARNING]
> The endpoint reads `total` as a machine number. `"1.249,00"` is not a number here and becomes 0. Only track.js cleans Danish amounts – send `1249` or `1249.00` from your server.
Legacy direct links without a click ID can send `aff` (or `aff_id`) – the affiliate's tracking ID – together with `store` instead. The affiliate must then have joined the store's program, otherwise the response is `not_joined`.
## GET
Parameters in the URL. This is what track.js uses as a fallback when the browser has no `sendBeacon`.
```bash title="GET"
curl "https://app.affilyflow.com/api/track/conversion?afid=c_AbC123xYz_-9&store=DIN_BUTIK&order=1008&total=499¤cy=DKK"
```
## POST
Send the fields as JSON in the body. The fields can also be in the URL – if a field is in both, the body wins. This is how track.js sends with `navigator.sendBeacon`: a `POST` with the fields in the URL and no body.
```bash title="POST with JSON"
curl -X POST https://app.affilyflow.com/api/track/conversion \
-H "Content-Type: application/json" \
-d '{"afid":"c_AbC123xYz_-9","store":"DIN_BUTIK","order":"1008","total":499,"currency":"DKK"}'
```
```js title="Node · after payment"
await fetch('https://app.affilyflow.com/api/track/conversion', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
afid: order.afClick, // saved from the af_click cookie
store: 'DIN_BUTIK',
order: order.number,
total: order.subtotalExVat, // goods after discount, excl. VAT and shipping
currency: order.currency
})
});
```
The body may be at most 64 KB.
### Optional body fields
| Field | Meaning |
|---|---|
| `items` | Item lines: a list of `{ title, variant, qty, price, product_id }`. `title` is required per line. At most 50 lines; long text is truncated. |
| `subtotal`, `tax`, `shipping` | The order breakdown (numbers, 0 or above). See below. |
| `buyer_hash` | Fingerprint of the buyer's email: `hex(sha256(lowercase(trim(email))))` – 64 hex characters. If it matches the affiliate's email, the sale is recorded as rejected (`self_referral`). The email itself is never sent. |
**The breakdown:** if you send `total` as the order's full amount and include `subtotal`, commission is calculated on `subtotal` instead – but never on more than `total`:
- If `tax` is 0 or missing, or `subtotal + shipping + tax = total` (VAT added on top), the base is `subtotal`.
- If `subtotal + shipping = total` (VAT included in prices), the base is `subtotal × (total − tax) / total`.
- Otherwise the cautious `min(subtotal, total − tax − shipping)` is used.
The simplest is to send `total` as the goods amount excluding VAT and shipping and leave out the breakdown.
## Responses
The response is always JSON with a `status`. What each status means and what to do is in [Status codes](https://www.affilyflow.com/docs/en/reference/status-codes).
| HTTP | Body | When |
|---|---|---|
| 200 | `{ "status": "ok" }` | The sale is recorded. |
| 200 | `{ "status": "duplicate" }`, `"expired"` etc. | The request was processed, but the sale earned no commission. |
| 404 | `{ "status": "unknown" }` | The click ID doesn't exist. |
| 400 | `{ "status": "error", "reason": "…" }` | Neither `afid` nor `aff` + `store` was included. |
| 413 | `{ "status": "too_large" }` | The body was over 64 KB. |
| 429 | `{ "status": "rate_limited" }` | Too many calls from the same IP address. |
| 500 | `{ "status": "error" }` | Recording failed. Try again. |
A recorded sale awaits the store's approval. Commission is only credited once the sale is approved.
## Duplicates and order numbers
- The same order number in the same program is never counted twice – the second call responds `duplicate`. It is therefore safe to resend when in doubt.
- Without an order number there is no duplicate check on the server. Always send the order number.
- An unfilled placeholder (`DIN_ORDRE_ID`, `ORDER_ID`, `{{ … }}`, `[ … ]`, `< … >`, `% … %` etc.) is treated as no order number, so it never takes up the number for every later sale.
## Currency
The sale can be in any currency with a rate from the European Central Bank. It is converted to the store's settlement currency before commission and fee are calculated, and the rate is locked on the sale. A currency code that isn't three letters, or a currency without a rate from the last 10 days, is rejected as `unsupported_currency`.
## Limits
- The endpoint is rate limited per IP address. The limit doesn't affect a store's own server in normal use; the response is `429` with `rate_limited` if it is reached.
- Each program has an amount cap per order (50,000 in the settlement currency by default). An order above the cap is rejected as `value_too_high` – typically an amount sent in cents.
- Long values are truncated rather than rejected: click ID 64 characters, store and order number 120 characters.
## Refunds
The endpoint only reports sales. If an order is refunded, the store rejects the sale under Sales in the dashboard within the program's approval period. `/api/track/conversion/reverse` is used by the WooCommerce plugin and requires the store's key – it is not a public route.
Kilde: https://www.affilyflow.com/docs/en/reference/conversion-api
---
# Status codes
> What each status from the conversion endpoint means – and what to do about it.
Every sale reported to Affilyflow gets a status. You see it in the response from the [conversion endpoint](https://www.affilyflow.com/docs/en/reference/conversion-api) (`{ "status": "…" }`) and in the live test under Setup in the store dashboard. Many statuses are perfectly normal – only some mean the setup needs fixing.
## Recorded
| Status | Meaning | What to do |
|---|---|---|
| `ok` | The sale is recorded and attributed to an affiliate – or to the store's test link, if it was a test purchase. It now awaits the store's approval. | Nothing. |
## Normal – not counted
| Status | Meaning | What to do |
|---|---|---|
| `duplicate` | The same order number came again in the same program and wasn't counted twice – e.g. a reloaded receipt. | Nothing. If it happens on every sale, check that the order number isn't the same every time. |
| `expired` | The click is older than the program's cookie window. The window is locked on the click. | Nothing. When testing: open the test link again and buy right after. |
| `unknown` | The click ID doesn't exist – deleted, wrong or never recorded. HTTP 404. | Check that you send the click ID unchanged (`c_…`). When testing: open the test link again and buy in the same browser. |
| `blocked` | The store has blocked the affiliate, so the sale earned no commission. | Nothing. |
| `suspended` | The affiliate is suspended, so the sale earned no commission. | Nothing. |
| `store_inactive` | The store is set to inactive, so the sale earned no commission. | Write to info@affilyflow.com if it is a mistake. |
## Fix the setup
| Status | Meaning | What to do |
|---|---|---|
| `store_mismatch` | The click belongs to a different store than the one the sale was reported for. | Check that `data-store` (or `store`) is your own store ID from Setup. |
| `invalid_value` | The amount was negative. | Send the goods amount after discount, excluding VAT and shipping, as a positive number. |
| `zero_value` | The program pays a fixed amount per sale, but the order was 0. | Check that the amount comes through. On the receipt: check the amount field. |
| `value_too_high` | The order was above the program's amount cap (50,000 in the settlement currency by default) and wasn't recorded. | Check that the amount wasn't sent in cents. If it is a real sale, write to info@affilyflow.com. |
| `unsupported_currency` | The currency couldn't be converted: the code isn't three letters, or there is no rate from the last 10 days. | Send an ISO 4217 code such as `DKK` or `EUR`. If the code is right, write to info@affilyflow.com. |
| `stale_lead_trigger` | A lead (an order number starting with `lead_`) was reported to a program that pays per sale. | Remove the lead code or `data-lead-url`, and use the sale code. |
| `program_inactive` | The program was paused when the sale came in. | Turn the program back on if the pause wasn't intended. |
| `not_joined` | Only for legacy links without a click ID: the affiliate hasn't joined the program. | Nothing – the affiliate needs to use a link from their dashboard. |
## Rejected under the rules
These sales are recorded but shown as rejected with the reason – without commission, fee or notifications.
| Status | Meaning | What to do |
|---|---|---|
| `self_referral` | The buyer's email is the same as the affiliate's (terms §7). Requires the integration to send `buyer_hash`. | Nothing. |
| `rule_breach` | The click broke the program's rules – a coupon site, forbidden paid search or a channel the program doesn't allow. Only when Affilyflow enforces program rules. | Nothing. The store sees the breach under Rule breaches. |
## Endpoint errors
| Status | HTTP | Meaning | What to do |
|---|---|---|---|
| `error` | 400 | Neither a click ID nor `aff` + `store` was included. | Send `afid`. |
| `error` | 500 | Recording failed. The sale may not be saved. | Try again with the same order number – a duplicate isn't counted twice. |
| `rate_limited` | 429 | Too many calls from the same IP address. | Wait a moment and try again. |
| `too_large` | 413 | The body was over 64 KB. | Send fewer item lines. |
## From the integrations
The Stripe webhook, the WooCommerce plugin and the Shopify app report sales too, and these statuses can appear in their events:
| Status | Meaning |
|---|---|
| `no_affiliate` | The sale came without a click or affiliate – a normal sale. |
| `not_recurring` | A subscription renewal where the click didn't qualify for subscription commission. |
| `first_rejected` | A renewal of a subscription whose first sale was rejected – no commission. |
| `cap_reached` | The subscription has reached the number of payments that earn commission. |
| `test_mode` | Stripe in test mode isn't booked. |
| `unpaid` | The payment wasn't completed. |
| `unattributed` | Shopify: a purchase without a click ID while the store is testing. Only the time is stored, and it is never credited. |
For Stripe, `unknown` and `expired` are common: a checkout without an affiliate has no click.
Kilde: https://www.affilyflow.com/docs/en/reference/status-codes
---
# REST API
> The affiliate API under /api/v1 – keys, limits, every endpoint with parameters and responses, pagination and error codes.
The REST API gives your own scripts and integrations access to the same data as the affiliate dashboard: programs, rules, products, links, sites, clicks, sales, balance and payouts. Every endpoint maps to a tool on the [MCP server](https://www.affilyflow.com/docs/en/affiliate/mcp) – the same functions sit behind both.
The API is for affiliates only. Stores have no API.
## Get started
1. Open **AI og API** in the dashboard menu (under Tjen penge – the address is `app.affilyflow.com/indstillinger/api`).
2. Create an API key and give it a name, e.g. "My site".
3. Copy the key right away. It is shown only once, and we store only a fingerprint of it.
Send the key in the `Authorization` header on every request:
```bash title="List your programs"
curl https://app.affilyflow.com/api/v1/programs \
-H "Authorization: Bearer $AFFILYFLOW_API_KEY"
```
The header `X-API-Key: afk_…` also works. A key always starts with `afk_`.
> [!WARNING]
> The key gives access to your links and numbers. Keep it on the server as an environment variable – never in code that runs in the browser or in a public repository. Revoke a key under **AI og API** if it has been shared. You can have at most 10 active keys.
`GET https://app.affilyflow.com/api/v1` needs no key and returns a machine-readable overview of every endpoint, parameter and limit.
OAuth connections (claude.ai, ChatGPT, Claude Code) only work on the MCP server – not on the REST API.
## Conventions
| Topic | Rule |
|---|---|
| Base URL | `https://app.affilyflow.com/api/v1` |
| Format | JSON in and out. Send `Content-Type: application/json` with a body. |
| Amounts | Danish kroner (DKK) excluding VAT. Sales in other currencies are converted at a locked ECB rate; the original is in `original_currency` and `original_value`. |
| Dates | `from` and `to` as `YYYY-MM-DD` in Danish time (Europe/Copenhagen), both inclusive. Without them: the last 30 days. At most 366 days per request. |
| IDs | Programs, links, sites and sales have UUIDs. `click_id` is an integer. |
| Pagination | `limit` + `cursor`. The response has `total`, `has_more` and `next_cursor`. Pass `next_cursor` back as `cursor` with the same filters. |
| Errors | `{ "error": { "code": "…", "message": "…" } }` with an HTTP status. Codes are stable identifiers; messages are in Danish. |
| Language | Data labels such as `status_text` and notes are in Danish. |
| Cache | Responses are sent with `Cache-Control: no-store`. |
Rejected sales never count as sales or commission in the numbers.
## Limits
| Limit | Value | Error |
|---|---|---|
| Requests per minute per key | 120 | HTTP 429 `rate_limited` with `Retry-After: 60` |
| Write requests per minute per key | 60 (a batch request counts as one) | HTTP 429 `rate_limited` |
| New links per 24 hours per account | 500 (every new link on the account in the last 24 hours counts) | HTTP 429 `daily_link_limit` |
| Links per batch | 50 | HTTP 400 `invalid_input` |
A reused identical link does not count as a new link. Append `?sid=` (another sub-ID) or `?p=` (another page at the store) to an existing link instead of creating many near-identical links.
## Endpoints
| Method | Path | Use | MCP tool |
|---|---|---|---|
| GET | `/account` | Your account, quota and dashboard links | `get_account` |
| GET | `/catalog` | Search all open programs | `search_programs` |
| GET | `/programs` | Programs you have joined | `list_programs` |
| GET | `/programs/{id}` | One program with rules and guidelines | `get_program` |
| GET, POST | `/programs/{id}/compliance` | Check a promotion against the rules | `check_compliance` |
| GET | `/products` | Search the stores' products | `search_products` |
| GET | `/websites` | Your registered sites | `list_websites` |
| POST | `/websites` | Add a site | `add_site` |
| PATCH | `/websites/{id}` | Edit a site | `update_site` |
| GET | `/links` | Your links | `list_links` |
| POST | `/links` | Create a link | `create_link` |
| POST | `/links/batch` | Up to 50 links in one request | `create_links_batch` |
| GET | `/links/{id}` | One link with numbers | `get_link` |
| PATCH | `/links/{id}` | Change name, landing page or site label | `update_link` |
| POST | `/links/{id}/archive` | Archive a link | `archive_link` |
| DELETE | `/links/{id}/archive` | Restore an archived link | `restore_link` |
| GET | `/stats` | Clicks, sales and commission for a period | `get_stats` |
| GET | `/sales` | Individual sales | `list_sales` |
| GET | `/clicks` | Individual clicks with page and source | `list_clicks` |
| GET | `/sites` | Measured sites | `get_sites` |
| GET | `/pages` | Pages on your sites | `get_pages` |
| GET | `/balance` | Balance and next payout | `get_balance` |
| GET | `/payouts` | Your payouts | `list_payouts` |
## Account
### GET /account
Returns `account` (AF number, display name and how stores see you, country, niche, number of sites, links and joined programs, currency and timezone), `access`, `quota` (links in the last 24 hours, remaining, limits and requests used in the current minute) and `dashboard` with the addresses of the dashboard pages.
## Programs
### GET /catalog
The full catalog of open programs – including programs you have not joined.
| Parameter | Type | Description |
|---|---|---|
| `query` | text | Free text matched against store name, category, description and audience. |
| `category` | text | Category (Danish label), e.g. `Bolig`. The response lists them in `categories`. |
| `joined` | `true` / `false` | Only joined or only not joined. Omit for both. |
| `min_commission_pct` | number 0–100 | Minimum percentage commission. Fixed-amount programs are excluded. |
| `recurring_only` | `true` / `false` | Only programs that pay commission on subscription renewals. |
| `channel` | text | Only programs that allow the channel, e.g. `TikTok`. |
| `sort` | `epc`, `commission`, `newest`, `name` | Default: `epc` (network earnings per click). |
| `limit` | 1–50 | Default 20. |
| `cursor` | text | From `next_cursor`. |
Each program includes `program_id`, `store`, `joined`, `commission` (`type`, `value`, `text`), `cookie_days`, `approval_days`, `recurring_months`, `allowed_channels`, `paid_search` (`allowed`, `no_brand` or `forbidden`), `allow_coupon_sites`, `network` (EPC, conversion, order value and approval rate – rejected sales excluded) and `join_url`. Joining happens in the dashboard via `join_url` – the API never joins programs.
### GET /programs
The programs you have joined, with commission, cookie days, channels, the saved rules and the store guidelines (truncated). `active: false` means new sales give no commission; `inactive_reason` says why (`program_paused` or `store_inactive`). The field `brand_bidding` is deprecated – use `paid_search`. Parameter: `search` (store name).
### GET /programs/{id}
Everything about one program: the store's description and audience, the guidelines verbatim, the rules that apply today (`rules.now`), the saved rules (`rules.saved`) and the date a tightening takes effect (`rules.tightening_effective_from`), a `compliance_summary`, the rule-watch mode (`enforcement.mode`: `shadow` or `enforce`), discount codes, creatives (banners with ready HTML and your link once joined), top products, network stats and your own numbers for the program (`mine`).
### GET or POST /programs/{id}/compliance
Checks a planned promotion against the program's measurable rules. Send at least one parameter. Use POST with JSON when `content` is long.
| Parameter | Description |
|---|---|
| `channel` | Planned channel, e.g. `Instagram`, `SEO`, `E-mail`. |
| `placement_url` | Full URL of the page where the link will be placed. |
| `landing_page` | Path or URL at the store. |
| `ad_type` | `paid_search_brand`, `paid_search_generic`, `coupon_site`, `cashback_site`, `social_post`, `newsletter` or `website_content`. |
| `content` | Draft text (max 20,000 characters) – checked for an advertising label. |
The response has `verdict` (`ok`, `warning` or `not_allowed`), `checks` with a verdict and a reason per rule (plus `effective_from` when a tightening is coming), the store guidelines verbatim and the rule-watch mode. Only measurable rules are judged: channel, coupon and cashback sites from the placement's domain, paid search as declared, and the landing page. The advertising-label check is heuristic, and the store's free-text guidelines are not judged.
```bash title="May I run Google Ads on generic keywords?"
curl "https://app.affilyflow.com/api/v1/programs/PROGRAM_ID/compliance?ad_type=paid_search_generic" \
-H "Authorization: Bearer $AFFILYFLOW_API_KEY"
```
## Products
### GET /products
Searches the stores' products across open programs.
| Parameter | Type | Description |
|---|---|---|
| `query` | text | Search words (the catalogs are mostly Danish), e.g. `sofa`. Empty = the products you earn most on. |
| `program_id` | UUID | Only one program. |
| `min_commission_pct` | number 0–100 | Minimum percentage commission. |
| `in_stock_only` | `true` / `false` | Hide out-of-stock products. |
| `sort` | `earnings`, `relevance`, `price`, `rate` | Default: `relevance` with a query, otherwise `earnings`. |
| `limit` | 1–60 | Default 20. |
| `cursor` | text | From `next_cursor`. |
Each product has `product_id`, `title`, `url`, `image_url`, `price`, `currency`, `in_stock`, `store`, `program_id`, `commission`, `est_commission` and `joined`. For joined programs, `tracking_url` is your existing link sent straight to the product (`?p=`). `est_commission` is an estimate: the rate applied to the price excluding VAT, or the fixed amount.
## Sites
### GET /websites
Your registered sites with `website_id`, `domain`, `name`, `niche`, `monthly_traffic` and `created_at`.
### POST /websites
Adds a site with the same rules as **Mine sites** in the dashboard.
| Field | Description |
|---|---|
| `domain` | Required. E.g. `mysite.com`. A full URL is accepted and reduced to the domain. |
| `name` | Display name, max 120 characters. Default: the domain. |
| `niche` | E.g. `Home`, max 80 characters. |
| `monthly_traffic` | Approximate monthly visitors. |
A new site returns HTTP 201. If you already have the domain, nothing is created, and the response is the existing site with `already_existed: true` and HTTP 200. The response includes `history` with the clicks, sales and commission that already came from the domain.
### PATCH /websites/{id}
Changes `domain`, `name`, `niche` or `monthly_traffic`. Send `null` or `""` to clear `name`, `niche` or `monthly_traffic`. A domain you already use on another site is rejected with `duplicate_domain`. Sites can only be deleted in the dashboard.
### GET /sites
Sites measured from clicks – registered and discovered – with clicks, sales, commission, EPC, page-tracking coverage and method, top stores and top pages. `suggest_add: true` marks a discovered site with at least 3 clicks or a sale that you have not added.
| Parameter | Description |
|---|---|
| `from`, `to` | Optional period. Without: all time. |
| `pages_per_site` | 0–25 top pages per site. Default 5. |
| `include_minor` | Include discovered sites with very few clicks. |
### GET /pages
Pages on your sites ranked by commission, with clicks, sales, EPC, conversion, best-selling product, stores and 30-day trend. Clicks without a known page appear as one row per site with `known: false`.
| Parameter | Description |
|---|---|
| `from`, `to` | Optional period. Without: all time. |
| `site` | Only pages on this site (domain without www). |
| `known_only` | Hide the rows without a known page. |
| `min_clicks` | Minimum clicks. |
| `limit` | 1–200. Default 50. |
| `cursor` | From `next_cursor`. |
## Links
### GET /links
Your links, newest first.
| Parameter | Description |
|---|---|
| `program_id` | Only links for this program. |
| `sub_id` | Only links with this sub-ID. |
| `search` | Text in name, store, destination or sub-ID. |
| `archived` | `exclude` (default), `include` or `only`. |
| `include_stats` | `true` adds clicks, sales, commission, EPC and conversion per link. |
| `limit` | 1–200. Default 50. |
| `cursor` | From `next_cursor`. |
Each link has `link_id`, `url`, `program_id`, `store`, `active`, `name`, `landing_page`, `destination`, `sub_id`, `channel`, `utm`, `website_id`, `placement_url`, `archived`, `created_at` and `html`.
### POST /links
Creates a link – exactly like the link generator in the dashboard. You must have joined the program.
| Field | Description |
|---|---|
| `program_id` | Required. From `/catalog` or `/programs`. |
| `landing_page` | Path or full URL on the store's domain, e.g. `/products/sofa`. Default: the front page. |
| `sub_id` | Max 80 characters. |
| `campaign` | Max 80 characters. Sent as `utm_campaign` and used as the link name. |
| `channel` | One of the program's allowed channels: `SEO`, `Blog`, `Instagram`, `TikTok`, `YouTube`, `Facebook`, `Pinterest`, `E-mail`, `Website`. |
| `utm_content`, `utm_term` | Max 100 characters each. |
| `website_id` | Optional site label from `/websites`. |
| `placement_url` | Full URL of the page where the link will be placed (`https://…`). |
| `name` | The link's name in the dashboard, max 120 characters. |
| `include_qr` | `true` adds a QR code as PNG in `qr_png_base64`. |
```bash title="Create a link"
curl -X POST https://app.affilyflow.com/api/v1/links \
-H "Authorization: Bearer $AFFILYFLOW_API_KEY" \
-H "Content-Type: application/json" \
-d '{"program_id":"PROGRAM_ID","landing_page":"/products/sofa","sub_id":"blog-best-sofas-oct"}'
```
A new link returns HTTP 201. If an identical link exists, it is reused with `reused: true` and HTTP 200.
```json title="Response (shortened)"
{
"link": {
"url": "https://app.affilyflow.com/go/3f9a1c07be",
"short_code": "3f9a1c07be",
"reused": false,
"program_id": "PROGRAM_ID",
"store": "Store",
"landing_page": "/products/sofa",
"sub_id": "blog-best-sofas-oct",
"html": "Store",
"variants": {
"with_sub_id": "https://app.affilyflow.com/go/3f9a1c07be?sid={sub_id}",
"deeplink": "https://app.affilyflow.com/go/3f9a1c07be?p={path_on_store}"
},
"warnings": []
},
"warnings": [],
"quota": {
"links_created_last_24h": 1,
"links_remaining_24h": 499,
"daily_link_limit": 500,
"requests_per_minute": 120,
"requests_used_this_minute": 1,
"write_calls_per_minute": 60
}
}
```
`warnings` holds rule warnings for the program – e.g. a channel or placement that is not allowed, or a tightening that is coming. Use `html` on your site: its `referrerpolicy` makes every click report the exact page it came from (Chrome, Edge and Firefox; Safari and some apps send only the domain).
### POST /links/batch
Up to 50 links in one request. The top-level fields (`program_id`, `sub_id`, `campaign`, `channel`, `website_id`, `utm_content`, `utm_term`) are defaults that each element in `items` can override. An element can also have `landing_page`, `placement_url` and `name`.
```bash title="Links to three products"
curl -X POST https://app.affilyflow.com/api/v1/links/batch \
-H "Authorization: Bearer $AFFILYFLOW_API_KEY" \
-H "Content-Type: application/json" \
-d '{"program_id":"PROGRAM_ID","sub_id":"article-sofas-oct","items":[{"landing_page":"/products/sofa-a"},{"landing_page":"/products/sofa-b"},{"landing_page":"/products/sofa-c"}]}'
```
The response has a result per element (`index`, `ok` and either `link` or `error` with `code` and `message`), a `summary` (`requested`, `created`, `reused`, `failed`) and the remaining quota. A failing element does not stop the others.
### GET /links/{id}
One link with numbers, `performs_on` (the pages, sites or channels its clicks came from), the program status and the rules that apply today, and `variants`. `?include_qr=true` adds a QR code.
### PATCH /links/{id}
Changes `name`, `landing_page` (on the store's domain) or `website_id` (`null` removes the label). The URL never changes.
Sub-ID, campaign, channel, program and UTM values cannot be changed on an existing link, because its clicks are recorded with them. If you try, the API returns HTTP 400 `immutable_field`. Create a new link, or append `?sid=` to the existing one.
### POST and DELETE /links/{id}/archive
POST archives the link, DELETE restores it. An archived link keeps redirecting and tracking – it is only hidden from the list.
## Results
### GET /stats
Clicks, sales and commission for a period.
| Parameter | Description |
|---|---|
| `from`, `to` | Period. Default: the last 30 days. |
| `group_by` | `total` (default), `sub_id`, `program`, `link`, `day`, `site`, `page`, `source`, `device` or `country`. |
| `program_id` | Only this program. |
| `sub_id` | Only this sub-ID. |
| `compare` | `true` adds the previous period of equal length with change in percent. |
| `limit` | 1–500 rows when grouped. Default 100. `day` always returns every day. |
`totals` and each row in `rows` have `clicks`, `sales`, `sales_pending`, `sales_approved`, `sales_rejected`, `revenue`, `commission`, `commission_pending`, `commission_approved`, `conversion_rate_pct` and `epc`. Rows also have `key` and `label`. A sale counts on the day it was registered. With `site`, `page`, `source`, `device` and `country`, a sale is grouped by the click it came from.
```bash title="Last month per program, compared"
curl "https://app.affilyflow.com/api/v1/stats?from=2026-09-01&to=2026-09-30&group_by=program&compare=true" \
-H "Authorization: Bearer $AFFILYFLOW_API_KEY"
```
### GET /sales
Individual sales, newest first.
| Parameter | Description |
|---|---|
| `from`, `to` | Period by sale date. Default: the last 30 days. |
| `status` | `pending`, `approved` or `rejected`. |
| `program_id` | Only this program. |
| `limit` | 1–200. Default 50. |
| `cursor` | From `next_cursor`. |
Each sale has `sale_id`, `created_at`, `store`, `order_ref` (only the last characters of the order number), `order_value`, `commission_base` (when commission was calculated from another amount than the order), `commission` (after any partial refunds), `commission_before_refund`, `refunded_value`, original currency and rate, `status` and `status_text`, `rejection_reason` (e.g. *Regelbrud: …* for a rule breach), `approval_due_at` for pending sales, `items`, and the click: `click_id`, `clicked_at`, `link_id`, `sub_id` and `landing_page`.
### GET /clicks
Individual clicks with their full trail: time, the page the link was on and how it is known (`page_basis`), site, source with basis, link and sub-ID, landing page, device, country, the commission locked on the click and the sales the click led to. The period follows the click date.
| Parameter | Description |
|---|---|
| `from`, `to` | Period. Default: the last 30 days. |
| `site` | Only clicks from this site (domain without www). |
| `page` | Only clicks from pages starting with this URL – or `unknown` for clicks without a known page. |
| `link_id`, `program_id`, `sub_id` | Filters. |
| `source` | Only this source, e.g. `Google`, `Instagram`, `Direkte`. |
| `outcome` | `sale`, `no_sale` or `rejected` (only rejected sales). |
| `limit` | 1–200. Default 50. |
| `cursor` | From `next_cursor`. |
## Balance and payouts
Read-only. Responses never contain an IBAN (not even masked), CPR, tax ID or address, and the API cannot request a payout.
### GET /balance
`balance` (available, pending sales, in transit, total earned and paid out – all excluding VAT), `payout` (the next automatic payout day – the first Danish bank day of the month, minimum 100 DKK – whether you can request a payout now, and what is missing, with code, text and `you_can_fix`) and the 5 latest payouts.
### GET /payouts
All payouts, newest first: status, automatic or requested, amount excluding VAT, VAT, amount transferred, dates and the self-billing invoice number. Parameters: `limit` (1–100, default 24) and `cursor`.
## Error codes
| HTTP | Code | Meaning |
|---|---|---|
| 400 | `invalid_input` | A parameter is missing or invalid. The message says which. |
| 400 | `invalid_json` | The body is not valid JSON. |
| 400 | `invalid_cursor` | `cursor` is invalid. Start over without it. |
| 400 | `channel_not_allowed` | The channel is not allowed in the program. The message lists the allowed ones. |
| 400 | `landing_page_other_host` | The landing page is not on the store's domain. |
| 400 | `invalid_landing_page` | The landing page is not a valid path or URL. |
| 400 | `unknown_website` | `website_id` does not exist on your account. |
| 400 | `immutable_field` | Sub-ID, campaign, channel, program or UTM cannot be changed on an existing link. |
| 400 | `invalid_domain` | Write the domain as `mysite.com`. |
| 401 | `missing_key` | No key was sent. |
| 401 | `invalid_key` | The key is invalid or revoked. |
| 403 | `not_joined` | You have not joined the program. The message has the join link to the dashboard. |
| 403 | `inactive` | The program or the store is not active right now. |
| 403 | `account_suspended` | The account is suspended. |
| 404 | `not_found` | The program, link or site does not exist on your account. |
| 409 | `duplicate_domain` | You already have a site with that domain. |
| 429 | `rate_limited` | Too many requests. Wait and try again. |
| 429 | `daily_link_limit` | 500 new links in the last 24 hours. Reuse a link with `?sid=` or `?p=`. |
| 500 | `server_error`, `save_failed` | Something went wrong on our side. Try again. |
| 503 | `unavailable` | The API is temporarily unavailable. |
Asking for a link or site that belongs to another affiliate returns `not_found` – the API does not reveal that it exists.
## What the API cannot do
- Join programs – that happens in the dashboard, where the store is notified.
- Delete links or sites – links can be archived, sites are deleted only in the dashboard.
- See or change bank details, tax profile or profile – or request a payout.
Kilde: https://www.affilyflow.com/docs/en/affiliate/rest-api
---
# MCP server
> Connect Claude, ChatGPT, Claude Code or Cursor to your Affilyflow account – setup, sign-in, the 23 tools, prompts and limits.
The MCP server lets your AI assistant work directly with your Affilyflow account: find programs, read the store's rules, search products, create tracking links and fetch your clicks, sales, pages and balance – inside the conversation. The tools use the same functions as the dashboard and the [REST API](https://www.affilyflow.com/docs/en/affiliate/rest-api), so the numbers match.
The MCP server is for affiliates only.
## The address
```text title="MCP address"
https://app.affilyflow.com/api/mcp
```
The address is for an MCP client that sends POST requests. Opening it in a browser returns 405 Method Not Allowed – that is expected. Add the address as a connector in your assistant.
Before you start: you need an affiliate account, and you must have joined the programs you want links for. The assistant cannot join programs for you.
## Connect Claude
1. Open **Customize → Connectors** in Claude and choose **Add custom connector**.
2. Name the connector Affilyflow and paste the MCP address.
3. Choose **Connect**. Sign in to your Affilyflow account and approve the connection.
4. Enable Affilyflow under Connectors in the conversation. Ask the assistant to show your joined programs.
On a team account, an owner may need to add the connector to the organization first. See Anthropic's guide: [Get started with custom connectors](https://support.claude.com/en/articles/11175166-get-started-with-custom-connectors-using-remote-mcp).
## Connect ChatGPT
1. Open ChatGPT on the web. Find **Developer mode** under Settings → Apps → Advanced Settings, if it is available for your account.
2. Create a custom app named Affilyflow. Paste the MCP address and choose OAuth as authentication.
3. Sign in to Affilyflow and approve the connection. On a work account, your administrator may need to grant access first.
4. Select Affilyflow in the conversation. Ask ChatGPT to show the programs you have joined.
> [!NOTE]
> ChatGPT connects through Developer mode. Access, and whether it can create links, depends on your plan, workspace and OpenAI's current features. See OpenAI's guide: [Developer mode and MCP apps in ChatGPT](https://help.openai.com/en/articles/12584461-developer-mode-and-mcp-apps-in-chatgpt).
## Connect Claude Code
Run the command in your terminal:
```bash title="Terminal"
claude mcp add --transport http affilyflow https://app.affilyflow.com/api/mcp
```
Then open Claude Code, type `/mcp`, choose Affilyflow and complete the sign-in in your browser. No API key is needed. Because Claude Code runs on your own computer, the approval page shows a warning – only allow it if you just started the connection yourself.
## Connect Cursor and other MCP clients
Clients that cannot sign in with OAuth use an API key. Create one under **AI og API** in the dashboard (see [REST API](https://www.affilyflow.com/docs/en/affiliate/rest-api#get-started)) and add it to the client's configuration:
```json title="mcp.json"
{
"mcpServers": {
"affilyflow": {
"url": "https://app.affilyflow.com/api/mcp",
"headers": { "Authorization": "Bearer afk_YOUR_KEY" }
}
}
}
```
## Sign-in and access
Claude, ChatGPT and Claude Code connect with a sign-in instead of a key (OAuth 2.1 with PKCE). The assistant sends you to Affilyflow's own sign-in and approval page. It shows the app's name and the address you will be sent back to, so you can check that it is the right app. The assistant gets access to the MCP server – it never gets your password.
| The assistant can | The assistant cannot |
|---|---|
| Read programs, rules, products, your links, sites, clicks, sales, balance and payouts | Join programs |
| Create, rename and archive links | Delete links or sites |
| Add and edit your sites | See or change bank, CPR, tax or address details or your profile |
| | Request a payout |
- An access token lasts one hour and is renewed automatically. The renewal lasts 60 days and rotates on every use.
- Access from a sign-in works only on the MCP server – not on the REST API.
- Under **AI og API** in the dashboard you see your connected apps and can disconnect them. API keys are revoked in the same place. A disconnected app stops working from its next request.
The data you fetch through the assistant becomes part of the AI service you use. Choose the account and workspace you want to use for your affiliate results.
## The tools
The server has 23 tools. Read tools change nothing. Write tools only create, edit or archive your own links and sites – no tool deletes anything. Tool descriptions are in English; data and error messages are in Danish.
### Programs and rules
| Tool | Does |
|---|---|
| `search_programs` | Searches all open programs – including ones you have not joined – with commission, cookie window, approval period, channels, rules, network EPC and join link. |
| `list_programs` | The programs you have joined, with the saved rules and the store guidelines. |
| `get_program` | One program in depth: description, guidelines verbatim, today's rules and upcoming tightenings with date, discount codes, creatives with ready banner HTML, top products, network stats and your own numbers. |
| `check_compliance` | Checks a planned promotion against the program's measurable rules: channel, coupon and cashback sites, paid search, landing page and advertising label. Returns ok, warning or not allowed per rule. |
### Products and links
| Tool | Does |
|---|---|
| `search_products` | Searches the stores' products with price, stock, estimated commission and – for joined programs – a ready link to the product. |
| `create_link` | Creates or reuses a link with landing page, sub-ID, campaign, channel, UTM, name, HTML snippet and QR code. Write. |
| `create_links_batch` | Up to 50 links in one call with a result per link. Write. |
| `list_links` | Your links, newest first, optionally with numbers per link. |
| `get_link` | One link with numbers, the pages it performs on, the program status and today's rules. |
| `update_link` | Renames a link or changes its landing page or site label. Write. |
| `archive_link` | Hides a link from the list – it keeps working. Write. |
| `restore_link` | Shows an archived link again. Write. |
### Sites
| Tool | Does |
|---|---|
| `get_sites` | Sites measured from clicks with earnings, page-tracking coverage and suggestions for sites to add (`suggest_add`). |
| `get_pages` | The pages on your sites with clicks, sales, commission and trend. |
| `list_websites` | Your registered sites. |
| `add_site` | Adds a site. The numbers from clicks that already came from it are included. Write. |
| `update_site` | Fixes domain, name, niche or traffic – e.g. a domain missing its .dk. Write. |
### Results
| Tool | Does |
|---|---|
| `get_stats` | Clicks, sales and commission for a period – in total or by sub-ID, program, link, day, site, page, source, device or country, optionally compared with the previous period. |
| `list_sales` | Individual sales with status, rejection reason, approval date, refunds and the click. |
| `list_clicks` | Individual clicks with page, source, device, country and the sales the click led to. |
### Account and payouts
| Tool | Does |
|---|---|
| `get_account` | Your account, quota for new links and shortcuts to the dashboard. |
| `get_balance` | Balance, next payout day and what is missing. Read-only. |
| `list_payouts` | Your payouts with self-billing invoices. Read-only. |
Parameters and responses are the same as in the [REST API](https://www.affilyflow.com/docs/en/affiliate/rest-api). Every tool returns structured content plus the same JSON as text. `create_link` and `get_link` with `include_qr` return the QR code as an image.
## Prompts
In Claude and Claude Code (and other clients that support prompts) there are four ready-made workflows. Their text is in Danish, as it becomes your message in the conversation.
| Prompt | Does | Arguments |
|---|---|---|
| `monthly_report` | A monthly report with key numbers, best programs and pages, rejected sales and the next payout. | `month` (YYYY-MM, default: last month) |
| `find_programs_for_my_niche` | Finds the best open programs for your niche and explains their rules. | `niche`, `channel` |
| `optimize_links` | Reviews links, pages and page tracking and suggests improvements. | – |
| `build_product_links` | Finds products and creates links for them in one call. | `query` (required), `store`, `sub_id`, `count` (default 10, max 50) |
ChatGPT does not use prompts – the tools work without them.
## What the assistant is told
The server gives the assistant short instructions: how Affilyflow works, a typical workflow, that the store's rules and guidelines must be followed, that a discount code alone gives no commission, that advertising must be labelled under Danish marketing law, that amounts are in DKK excluding VAT, and that dates are in Danish time. How the assistant uses this is up to the model – check important numbers in the dashboard.
## Try it
- "Find the 10 sofas I earn most on at Nordlys Bolig and create links for them with sub-ID article-sofas-oct. Check the program rules first and give me the HTML for my site."
- "I want to make a TikTok video and run Google Ads on generic keywords for one of my programs. Am I allowed to? Show the store's guidelines and tell me if the rules are about to change."
- "Make a report for last month compared with the month before: best programs, pages and sources, rejected sales with reason, and when my next payout is."
- "Add the sites that send me clicks."
## Limits
| Limit | Value |
|---|---|
| Requests per minute | 120 per API key or connection |
| Write requests per minute | 60 per API key or connection |
| New links | 500 per 24 hours per account. A reused identical link does not count. |
| Links per batch | 50 |
## Errors
A tool that fails returns an error with a code the assistant can act on, for example:
| Code | Meaning |
|---|---|
| `not_joined` | You have not joined the program. Join in the dashboard and try again. |
| `channel_not_allowed` | The channel is not allowed in the program. |
| `landing_page_other_host` | The landing page is not on the store's domain. |
| `immutable_field` | Sub-ID, campaign and channel cannot be changed on an existing link. Create a new one, or use `?sid=`. |
| `daily_link_limit` | 500 new links in the last 24 hours. |
| `invalid_domain`, `duplicate_domain` | The domain is invalid or already on one of your sites. |
| `rate_limited` | Too many requests. Wait a moment. |
The full list is under [REST API → Error codes](https://www.affilyflow.com/docs/en/affiliate/rest-api#error-codes).
A 401 means the access has expired or been disconnected: connect the assistant again, or check that the API key has not been revoked.
## For client builders
- **Transport:** Streamable HTTP without sessions. Every POST stands alone, and there is no SSE stream on GET.
- **Protocol:** the server speaks `2025-11-25`, `2025-06-18`, `2025-03-26` and `2024-11-05` (with `initialize`) as well as the stateless `2026-07-28` (with `server/discover`, version and client capabilities in `params._meta`, and the headers `MCP-Protocol-Version`, `Mcp-Method` and `Mcp-Name`).
- **Access:** `Authorization: Bearer afk_…` (API key) or an OAuth access token. Without valid access the server returns 401 with `WWW-Authenticate: Bearer resource_metadata="…"`, which starts the sign-in in the client.
OAuth endpoints (Affilyflow is its own authorization server):
| Path | Purpose |
|---|---|
| `/.well-known/oauth-protected-resource/api/mcp` | Protected resource metadata (RFC 9728). |
| `/.well-known/oauth-authorization-server` | Authorization server metadata (RFC 8414). |
| `/oauth/authorize` | Sign-in and approval. |
| `/oauth/token` | Code exchange and refresh (`authorization_code`, `refresh_token`). |
| `/oauth/register` | Dynamic client registration. |
| `/oauth/revoke` | Revoke a token. |
PKCE with `S256` is required, the scope is `affiliate` (plus `offline_access` for refresh), and the token is bound to the resource `https://app.affilyflow.com/api/mcp`. Clients can identify themselves with a Client ID Metadata Document (a URL as `client_id`) or with dynamic registration. Redirect URIs must be https or loopback.
```bash title="Call a tool with an API key"
curl -X POST https://app.affilyflow.com/api/mcp \
-H "Authorization: Bearer $AFFILYFLOW_API_KEY" \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"get_stats","arguments":{"group_by":"program","compare":true}}}'
```
Kilde: https://www.affilyflow.com/docs/en/affiliate/mcp