Your first job advert, from sign-in to statistics
Four steps: get a token, send a job advert, put the script on the job ad's page, and read the statistics. The examples use the test environment.
Before you start
- A client id and client secret for the test environment, from Frantz.
- A job ad page you can put a script on. A test page of your own is fine.
- The examples read the id and secret from the environment variables
FRANTZ_CLIENT_IDandFRANTZ_CLIENT_SECRET.
The C#, JavaScript and Python examples are one program each, split into steps. The JavaScript runs in Node 18 or
later as an ES module (.mjs), and the Python uses requests.
-
Get an access token
Send the client id and secret to
POST /api/v2/auth/tokenas a form. The token inaccess_tokenworks for an hour (expires_inis in seconds). Send it asAuthorization: Beareron every other call.curl https://apitest.frantz.no/api/v2/auth/token \ -d grant_type=client_credentials \ --data-urlencode "client_id=$FRANTZ_CLIENT_ID" \ --data-urlencode "client_secret=$FRANTZ_CLIENT_SECRET" # Put access_token from the response in FRANTZ_TOKEN for the next steps.using System.Net.Http.Headers; using System.Net.Http.Json; var http = new HttpClient { BaseAddress = new Uri("https://apitest.frantz.no/") }; using var tokenResponse = await http.PostAsync("api/v2/auth/token", new FormUrlEncodedContent(new Dictionary<string, string> { ["grant_type"] = "client_credentials", ["client_id"] = Environment.GetEnvironmentVariable("FRANTZ_CLIENT_ID")!, ["client_secret"] = Environment.GetEnvironmentVariable("FRANTZ_CLIENT_SECRET")!, })); tokenResponse.EnsureSuccessStatusCode(); var token = await tokenResponse.Content.ReadFromJsonAsync<TokenResponse>(); http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token!.access_token); // At the end of the file: record TokenResponse(string access_token, string token_type, int expires_in, string scope);const base = "https://apitest.frantz.no"; const tokenResponse = await fetch(`${base}/api/v2/auth/token`, { method: "POST", body: new URLSearchParams({ grant_type: "client_credentials", client_id: process.env.FRANTZ_CLIENT_ID, client_secret: process.env.FRANTZ_CLIENT_SECRET, }), }); if (!tokenResponse.ok) throw new Error(`Token: ${tokenResponse.status} ${await tokenResponse.text()}`); const { access_token } = await tokenResponse.json(); const headers = { Authorization: `Bearer ${access_token}` };import os import requests base = "https://apitest.frantz.no" token = requests.post(f"{base}/api/v2/auth/token", data={ "grant_type": "client_credentials", "client_id": os.environ["FRANTZ_CLIENT_ID"], "client_secret": os.environ["FRANTZ_CLIENT_SECRET"], }) token.raise_for_status() headers = {"Authorization": f"Bearer {token.json()['access_token']}"}JSON{ "access_token": "fzat_...", "token_type": "Bearer", "expires_in": 3600, "scope": "job-adverts:write statistics:read" }Keep the token on your server and use it for its hour. Sign-in and scopes has the details.
-
Send a job advert
POST /api/v2/job-advertswith your own reference for the job ad inexternalReferenceand the ad's page inlandingPageUrl. Frantz answers201with the job advert's ConnectID (connectId) and thescriptfor the page.curl https://apitest.frantz.no/api/v2/job-adverts \ -H "Authorization: Bearer $FRANTZ_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "externalReference": "ad-12345", "landingPageUrl": "https://jobs.example.com/ad-12345", "title": "Nurse, Haukeland", "styrk": 2223, "endDate": "2026-11-30" }'using var sent = await http.PostAsJsonAsync("api/v2/job-adverts", new { externalReference = "ad-12345", landingPageUrl = "https://jobs.example.com/ad-12345", title = "Nurse, Haukeland", styrk = 2223, endDate = "2026-11-30", }); sent.EnsureSuccessStatusCode(); // 201 for a new job advert, 200 when you already had it var jobAdvert = await sent.Content.ReadFromJsonAsync<JobAdvert>(); Console.WriteLine($"ConnectID {jobAdvert!.ConnectId}: {jobAdvert.Script}"); // At the end of the file: record JobAdvert(int ConnectId, string ExternalReference, string Script);const sent = await fetch(`${base}/api/v2/job-adverts`, { method: "POST", headers: { ...headers, "Content-Type": "application/json" }, body: JSON.stringify({ externalReference: "ad-12345", landingPageUrl: "https://jobs.example.com/ad-12345", title: "Nurse, Haukeland", styrk: 2223, endDate: "2026-11-30", }), }); if (!sent.ok) throw new Error(`Job advert: ${sent.status} ${await sent.text()}`); // 201 for a new job advert, 200 when you already had it const jobAdvert = await sent.json(); console.log(`ConnectID ${jobAdvert.connectId}: ${jobAdvert.script}`);sent = requests.post(f"{base}/api/v2/job-adverts", headers=headers, json={ "externalReference": "ad-12345", "landingPageUrl": "https://jobs.example.com/ad-12345", "title": "Nurse, Haukeland", "styrk": 2223, "endDate": "2026-11-30", }) sent.raise_for_status() # 201 for a new job advert, 200 when you already had it job_advert = sent.json() print(f"ConnectID {job_advert['connectId']}: {job_advert['script']}")JSON{ "connectId": 744376, "externalReference": "ad-12345", "script": "<script crossorigin='anonymous' async='' data-r='744376' data-frpixel='744376' src='https://...'></script>", "title": "Nurse, Haukeland", "landingPageUrl": "https://jobs.example.com/ad-12345", "startDate": "2026-10-19", "endDate": "2026-11-30", "audienceId": 1421, "styrk": 2223, "reportUrl": "https://...", "createdDate": "2026-10-19T08:15:02Z", "updatedDate": "2026-10-19T08:15:02Z" }Sending the same
externalReferenceagain updates the job advert you have and answers200, so the call is safe to repeat after a timeout.styrkis the job's STYRK-08 occupation code, which Frantz uses to find the audience; Audiences shows the other way. -
Put the script on the job ad's page
Insert the
scriptvalue as it is, at the end of<head>on the page inlandingPageUrl. From then on Frantz measures the visitors who allow it.HTML<head> <title>Nurse, Haukeland</title> <!-- other tags --> <script crossorigin='anonymous' async='' data-r='744376' data-frpixel='744376' src='https://...'></script> </head>Store the whole
scriptvalue, not just the ConnectID, and replace it when a response gives you a new one. Frantz may change the script's address or attributes, and the job advert always has the current one. -
Read the statistics
GET /api/v2/job-adverts/{connectId}/statisticsgives one entry per channel the job ad got visitors through. The numbers are counted during the night, so read them once a day, from a background job.curl https://apitest.frantz.no/api/v2/job-adverts/744376/statistics \ -H "Authorization: Bearer $FRANTZ_TOKEN"var statistics = await http.GetFromJsonAsync<JobAdvertStatistics>($"api/v2/job-adverts/{jobAdvert.ConnectId}/statistics"); foreach (var channel in statistics!.Channels) Console.WriteLine($"{channel.Name}: {channel.PageViews} page views, {channel.ApplyClicks} apply clicks"); // At the end of the file: record JobAdvertStatistics(int ConnectId, DateOnly? DataUntil, List<ChannelStatistics> Channels); record ChannelStatistics(int? ChannelId, string? Network, string Name, int? Impressions, int? Clicks, int PageViews, int ReadMoreClicks, int Readers, int ApplyClicks, decimal AvgTimeSeconds);const response = await fetch(`${base}/api/v2/job-adverts/${jobAdvert.connectId}/statistics`, { headers }); if (!response.ok) throw new Error(`Statistics: ${response.status} ${await response.text()}`); const statistics = await response.json(); for (const channel of statistics.channels) console.log(`${channel.name}: ${channel.pageViews} page views, ${channel.applyClicks} apply clicks`);response = requests.get(f"{base}/api/v2/job-adverts/{job_advert['connectId']}/statistics", headers=headers) response.raise_for_status() for channel in response.json()["channels"]: print(f"{channel['name']}: {channel['pageViews']} page views, {channel['applyClicks']} apply clicks")JSON{ "connectId": 744376, "from": null, "to": null, "dataUntil": "2026-10-25", "channels": [ { "channelId": 90211, "network": "meta", "name": "Meta", "impressions": 15230, "clicks": 412, "pageViews": 388, "readMoreClicks": 141, "readers": 97, "applyClicks": 31, "avgTimeSeconds": 74.5 }, { "channelId": null, "network": null, "name": "Direktetrafikk", "impressions": 0, "clicks": 0, "pageViews": 52, "readMoreClicks": 20, "readers": 14, "applyClicks": 6, "avgTimeSeconds": 81.2 } ] }Statistics explains each number and when it is updated.
Going live
Ask Frantz for a client for production, and change the address to https://api.frantz.no. Job adverts
and ConnectIDs from the test environment do not exist in production, so send your job adverts again there.