FrantzPublic API
Guide - preview

Campaigns

A campaign advertises a job ad for an advertiser. You ask Frantz for a quote, create a draft on it, and order it. A media advisor at Frantz then sets the campaign up and publishes it, as for campaigns ordered in Frantz Business Manager.

Preview. The campaigns are for partners in a pilot with Frantz. Reading and quotes need the scope campaigns:read. Creating, changing, ordering, cancelling, pausing and stopping need campaigns:write, which Frantz gives on request, since an order costs the advertiser money.

The steps

  1. Find the advertiser, the place of work and the audience.
  2. Ask for a quote, and show your user the price and the expected visits.
  3. Create a draft on the quote, with your reference for the campaign and the job advert it is for.
  4. Add the material the media advisor needs, such as images, video and ad texts.
  5. Order the draft.
  6. Follow its status while Frantz sets it up and runs it.
CallWhat it doesScope
GET/api/v2/advertisersThe advertisers you can order for.campaigns:read
GET/api/v2/media-productsThe media products you can choose.campaigns:read
GET/api/v2/geographies?search=Find the place of work.campaigns:read
GET/api/v2/audiencesThe audiences. See Audiences.campaigns:read or job-adverts:write
POST/api/v2/campaign-quotesAsk for a quote.campaigns:read
GET/api/v2/campaign-quotes/{quoteId}Read a quote again.campaigns:read
POST/api/v2/campaignsCreate a draft.campaigns:write
PATCH/api/v2/campaigns/{campaignId}Change a draft.campaigns:write
POST/api/v2/campaigns/{campaignId}/creativesAdd material to a draft.campaigns:write
GET/api/v2/campaigns/{campaignId}/creativesThe material of a campaign.campaigns:read
POST/api/v2/campaigns/{campaignId}/orderOrder a draft.campaigns:write
POST/api/v2/campaigns/{campaignId}/cancelCancel a campaign before it is set up.campaigns:write
POST/api/v2/campaigns/{campaignId}/pausePause a campaign that is set up.campaigns:write
POST/api/v2/campaigns/{campaignId}/resumeStart a paused campaign again.campaigns:write
POST/api/v2/campaigns/{campaignId}/stopEnd a campaign before its last day.campaigns:write
GET/api/v2/campaignsList your campaigns, or find one by your reference.campaigns:read
GET/api/v2/campaigns/{campaignId}Get a campaign.campaigns:read

When a rule stops a request, the answer is 422, and Errors describes each rule.

Advertisers and media products

The advertiser is the employer the ads run for. You can order for your own company, and for the employers you have an agreement with Frantz to order for. GET /api/v2/advertisers lists them.

A new advertiser is added in Frantz Business Manager, with its organization number: by a user at your company who is allowed to add advertisers there, or by Frantz. From then on it is in the list.

JSON
{
  "items": [
    { "advertiserId": 5120, "name": "Example Health AS", "organizationNumber": "123456789" }
  ]
}

In a quote, give the advertiser as advertiserId, or as its nine-digit organizationNumber. The number must match exactly one of the advertisers you can order for. If you send both, they must be the same advertiser.

The media products are what a campaign buys, the same for every advertiser. Each runs on one or more networks: the media product Meta runs on meta, and a package can run on meta and linkedin. GET /api/v2/media-products lists the ones you can choose, each with the mediaProductId you choose it by in a quote's mediaProductIds.

A quote shares the budget between the media products. Frantz proposes media products for the audience, each with its share, and a quote without mediaProductIds uses them. To add one, send the proposed ones and the one you add; the quote's mediaProducts has the ids of the proposed ones. A media product Frantz does not propose for the audience counts as an average one: it gets the average share and the average recommended budget of the proposed ones, within its own min and max.

JSON
{
  "currency": "NOK",
  "items": [
    { "mediaProductId": 31, "name": "LinkedIn", "networks": ["linkedin"], "min": 3000, "max": 100000 },
    { "mediaProductId": 44, "name": "FINN.no", "networks": ["finn"], "min": 5000, "max": 50000 }
  ]
}

Add-ons and media packages from the agreements with Frantz cannot be chosen through the API yet.

The choices

A quote is made from these choices. They are the same when you send them straight to POST /api/v2/campaigns.

FieldWhat it is
advertiserId or organizationNumberRequired, one of them. The advertiser, see above.
titleRequired. The job title, up to 255 characters.
audienceId or styrkRequired, one of them. The audience, or the STYRK-08 code Frantz finds the audience for. See Audiences.
geographyIdRequired. The place of work. GET /api/v2/geographies?search=Berg gives the active places whose name starts with at least two letters you send, the exact name first.
materialRequired. hasMaterial when you send what the ads need, needHelp when Frantz makes it, as a paid add-on.
applicationDueDateThe application deadline, not in the past. It is the campaign's last day. Leave it out when the job has no fixed deadline, such as one filled as soon as possible. The campaign then runs 21 days from the day the quote is made.
mediaProductIdsThe media products you want, by mediaProductId, such as [12, 44]. Leave it out, and Frantz chooses them for the audience. To add one, send the proposed ones and the one you add. A media product that is neither proposed nor in GET /api/v2/media-products stops the quote with the rule media-product-not-available.
budgetThe budget for the media products. Leave it out to get the one Frantz recommends. It must be within the limits the quote gives.

Quotes

POST /api/v2/campaign-quotes works out the media products, the budget limits, the price and the expected visits, and keeps them as a quote. It answers 201 with the quote's quoteId, and its address in Location.

HTTP
POST /api/v2/campaign-quotes HTTP/1.1
Host: api.frantz.no
Authorization: Bearer fzat_...
Content-Type: application/json

{
  "organizationNumber": "123456789",
  "title": "Nurse, Haukeland",
  "styrk": 2223,
  "geographyId": 4601,
  "applicationDueDate": "2026-11-15",
  "mediaProductIds": [12, 31],
  "budget": 25000,
  "material": "needHelp"
}
JSON
{
  "quoteId": "3f2b9c0e8d4a4e6f9b1c2d3e4f5a6b7c",
  "request": {
    "advertiserId": 5120,
    "title": "Nurse, Haukeland",
    "audienceId": 1421,
    "geographyId": 4601,
    "applicationDueDate": "2026-11-15",
    "mediaProductIds": [12, 31],
    "budget": 25000,
    "material": "needHelp"
  },
  "createdDate": "2026-10-22T09:30:00Z",
  "validUntil": "2026-10-29T09:30:00Z",
  "budget": { "requested": 25000, "recommended": 30000, "min": 15000, "max": 60000, "applied": 25000 },
  "mediaProducts": [
    { "mediaProductId": 12, "name": "Meta", "networks": ["meta"] },
    { "mediaProductId": 31, "name": "LinkedIn", "networks": ["linkedin"] }
  ],
  "price": {
    "currency": "NOK",
    "budget": 25000,
    "addons": [ { "addonId": 12, "name": "Hjelp med materiell", "price": 2500 } ],
    "total": 27500
  },
  "estimate": { "pageViews": 1400, "readMoreClicks": 520, "readers": 360, "applyClicks": 95 },
  "startDate": "2026-10-22",
  "endDate": "2026-11-15"
}
  • A quote never changes. To try other choices, ask for a new quote.
  • A quote is valid for 7 days, until validUntil. A campaign on the quote is ordered at its price until then, also if the prices in the agreement change.
  • request is the choices the quote was made from, with the advertiser and the audience Frantz found for an organization number or a STYRK code.
  • budget has the budget you asked for (requested, null for the recommended one), the one Frantz recommends, the limits, and applied, the budget the price is for.
  • mediaProducts has each media product and the networks it runs on. The budget is for all of them together, in budget.applied.
  • price.total is the budget, and help with material when material is needHelp.
  • estimate is the visits Frantz expects over the campaign's period, worked out as in Frantz Business Manager. It is null when the estimate was not ready in time.
  • startDate and endDate are the period the campaign would have if it were set up today.

GET /api/v2/campaign-quotes/{quoteId} gives the quote again, with the same numbers, also after it has expired. Only you can read your quotes.

Ordering without a quote first

If you do not need to show the price first, send the choices to POST /api/v2/campaigns instead of a quoteId. Frantz makes the quote and sets its quoteId on the draft. When you order, Frantz makes it again at the prices of that day, so such a draft never has an expired quote. Send quoteId or the choices, not both.

Drafts

POST /api/v2/campaigns creates a draft. It needs your externalReference for the campaign, up to 100 characters and unique among your campaigns, and the quoteId of a quote that has not expired. A draft costs nothing, and Frantz does not see it until you order it.

FieldWhat it is
externalReferenceRequired when you create. Your id for the campaign. Cannot be changed.
quoteIdThe quote the campaign is ordered on. Everything the price depends on comes from it.
connectIdThe ConnectID of the job advert the campaign is for. Leave it out, and Frantz makes a job advert for the campaign, which you can read with GET /api/v2/job-adverts/{connectId}. A job advert can have one campaign.
landingPageUrlThe job ad's page, an https address.
commentA message to the media advisor, up to 4000 characters.
contactThe person at the advertiser Frantz can ask about the campaign: name, email and phone.
orderedByEmailThe person at your company who orders, by the email they sign in to Frantz Business Manager with. They get the order confirmation. Leave it out, and the campaign is ordered by the user who has been at your company the longest.
HTTP
POST /api/v2/campaigns HTTP/1.1
Host: api.frantz.no
Authorization: Bearer fzat_...
Content-Type: application/json

{
  "externalReference": "campaign-12345",
  "quoteId": "3f2b9c0e8d4a4e6f9b1c2d3e4f5a6b7c",
  "connectId": 744376,
  "landingPageUrl": "https://jobs.example.com/ad-12345",
  "contact": { "name": "Kari Nordmann", "email": "kari.nordmann@example.com" }
}
HTTP
HTTP/1.1 201 Created
Location: /api/v2/campaigns/90412
ETag: "00000000000007D9"
Content-Type: application/json; charset=utf-8

{
  "campaignId": 90412,
  "externalReference": "campaign-12345",
  "advertiserId": 5120,
  "title": "Nurse, Haukeland",
  "status": "draft",
  "startDate": null,
  "endDate": null,
  "price": { "currency": "NOK", "budget": 25000, "addons": [ { "addonId": 12, "name": "Hjelp med materiell", "price": 2500 } ], "total": 27500 },
  "channels": [],
  "reportUrl": null,
  "orderedDate": null,
  "createdDate": "2026-10-22T09:41:12Z",
  "updatedDate": "2026-10-22T09:41:12Z",
  "quoteId": "3f2b9c0e8d4a4e6f9b1c2d3e4f5a6b7c",
  "connectId": 744376,
  "landingPageUrl": "https://jobs.example.com/ad-12345",
  "contact": { "name": "Kari Nordmann", "email": "kari.nordmann@example.com" }
}

A new campaign with an externalReference you have used answers 409 and makes nothing. Find the campaign you have with GET /api/v2/campaigns?externalReference=campaign-12345.

Repeating a call safely

Creating a draft and adding material take an Idempotency-Key header: a value you make, such as a UUID, up to 100 characters. If the answer is lost, send the same call with the same key, and you get the first answer again, with the header Idempotent-Replayed: true, instead of a second draft or file.

  • A key is kept for 24 hours. After that, the same key starts a new call.
  • The same key with another call, another address or another body, answers 422 with the rule idempotency-key-reused.
  • While the first call with a key is still running, the same key answers 409. Wait, and send it again.
  • A call that answered 400 or 5xx is not kept, so you can fix it and send it again with the same key.

Changing a draft

PATCH /api/v2/campaigns/{campaignId} changes a draft. A field you leave out, or send as null, stays as it is, and an empty string clears a text field. To change what the price depends on, such as the budget or the media products, ask for a new quote and set its quoteId.

  • Send the ETag you got as If-Match. If the draft changed after you read it, the answer is 412: get it again and repeat the change.
  • Only a draft can be changed. A campaign that is ordered, or a draft Frantz is working on, answers 409.
HTTP
PATCH /api/v2/campaigns/90412 HTTP/1.1
Host: api.frantz.no
Authorization: Bearer fzat_...
Content-Type: application/json
If-Match: "00000000000007D9"

{
  "quoteId": "9a1d0c4b7e2f4a6c8b0d1e2f3a4b5c6d",
  "comment": "The ad should run in Bergen only."
}

Material

POST /api/v2/campaigns/{campaignId}/creatives adds material the media advisor uses when the campaign is set up: images, video, logos, other files and ad texts. Material can be added while the campaign is a draft, and goes to the media advisor with the order. GET /api/v2/campaigns/{campaignId}/creatives lists it.

A file is sent in two steps. First you say what it is, and the answer has an address to upload it to:

HTTP
POST /api/v2/campaigns/90412/creatives HTTP/1.1
Host: api.frantz.no
Authorization: Bearer fzat_...
Content-Type: application/json
Idempotency-Key: 6f1c2a9e-1b3d-4c5e-8f7a-9b0c1d2e3f4a

{
  "kind": "video",
  "fileName": "nurse-haukeland.mp4",
  "contentType": "video/mp4",
  "size": 18350080
}
HTTP
HTTP/1.1 201 Created
Content-Type: application/json; charset=utf-8

{
  "creativeId": 311,
  "kind": "video",
  "status": "awaitingUpload",
  "rejectedReason": null,
  "uploadUrl": "https://...blob.core.windows.net/uploads/attachments/...?sv=...&sig=...",
  "uploadExpires": "2026-10-22T10:41:12Z",
  "fileName": "nurse-haukeland.mp4",
  "contentType": "video/mp4",
  "size": 18350080,
  "headline": null,
  "primaryText": null,
  "description": null,
  "createdDate": "2026-10-22T09:41:12Z"
}

Then you send the file to uploadUrl with PUT, the header x-ms-blob-type: BlockBlob and the file as the body, before uploadExpires, one hour later. The file goes straight to Frantz' storage, not through the API. The address takes the file once: to send another file instead, add it as new material. Keep uploadUrl secret, since anyone with it can upload.

HTTP
PUT {uploadUrl} HTTP/1.1
x-ms-blob-type: BlockBlob
Content-Type: video/mp4

(the file)
kindFile names ending inLargest
image, logo.jpg, .jpeg, .png, .webp, .gif, .svg, with a content type starting with image/100 MB
video.mp4, with a content type starting with video/50 MB
fileThe above, and .pdf, .doc, .docx, .odt, .rtf, .txt, .xls, .xlsx, .html, .zip, .7z, .rar, .ttf, .otf, .woff, .woff2, .eot100 MB
  • A campaign can have up to 25 files. One more answers 422 with the rule too-many-files. A refused file does not count.
  • Frantz checks a file when you list the material and when you order. status goes from awaitingUpload to accepted, or to rejected with rejectedReason: the file was empty, too large, or not uploaded before uploadExpires.
  • Ad texts need no upload: send kind text with at least one of headline (up to 255 characters), primaryText (2000) and description (255). The media advisor gets them with the order.

Ordering

POST /api/v2/campaigns/{campaignId}/order, without a body, orders the draft at the price of its quote. It answers 202 Accepted with the campaign, now with status ordered. A media advisor at Frantz sets the campaign up and publishes it, and from then on it costs the advertiser money.

  • Ordering a campaign that is already ordered changes nothing, so the call is safe to repeat.
  • If the quote you set has expired, the answer is 422 with the rule quote-expired. Ask for a new quote, set its quoteId on the draft, and order again.
  • If Frantz made the quote from your choices, Frantz makes it again at the prices of the day you order.
  • A file you have added but not uploaded stops the order with the rule creative-awaiting-upload. Upload it, or wait until its uploadExpires has passed, and order again.
  • After the order, material can no longer be added. Send what the media advisor needs before you order.
  • Frantz invoices the campaign to your company. If your company has no invoice receiver in Frantz, the order stops with the rule no-invoice-receiver.
  • Frantz sets how many campaigns you can order through the API in a day, and how much they can cost together. An order that goes past either stops with the rule order-limit-reached. A day is a calendar day in Norway.

Cancelling

POST /api/v2/campaigns/{campaignId}/cancel, without a body, cancels a campaign that is not set up yet: a draft, or a campaign that is ordered or inReview. It costs nothing, and the answer is 200 with the campaign, now with status cancelled.

  • If the campaign was ordered, or a media advisor has started on it, Frantz takes it off the media advisor's list and tells the media advisor.
  • Once the campaign is set up, it can no longer be cancelled, and the answer is 409.
  • Cancelling a campaign that is already cancelled changes nothing, so the call is safe to repeat.
  • A cancelled campaign stays in your list with its reference, quote and fields. It no longer holds its job advert, which can then have another campaign, so connectId and orderedByEmail are empty. It cannot be changed or ordered again, and it keeps its externalReference: create a new campaign, with a new reference, instead.

Pausing, starting again and stopping

POST /api/v2/campaigns/{campaignId}/pause, /resume and /stop, without a body, change a campaign that is set up. The answer is 202 Accepted with the campaign.

  • On Meta, LinkedIn and BidTheatre, Frantz changes the channel through the network at once, and its status shows the change in the answer.
  • On the other networks, and when a network does not take the change, a media advisor makes it by hand. Until then the channel has pendingAction: pause, resume or stop.
  • pause pauses every channel that runs, and holds back the ones that have not started. resume starts the paused channels again, and takes back a pause that is still pending. stop ends every channel, for good: a stopped campaign cannot be started again.
  • Repeating a call changes nothing, so the calls are safe to repeat. A campaign that is not set up yet answers 409: cancel it instead. One that has ended answers 409 to pause and resume.

Statuses

statusMeans
draftCreated, not ordered. You can change it.
orderedOrdered, waiting for a media advisor.
inReviewA media advisor is working on it.
scheduledSet up and invoiced, and a channel has not started yet.
activeA channel is running. Also when the channels are in different states, such as one paused and one running.
pausedThe channels are paused, or paused and ended.
endedEvery channel has ended, because the campaign is past its last day or was stopped.
cancelledCancelled before it was set up.

channels is empty until the campaign is set up. Then each channel has its channelId, the same id as in the statistics, its network and its own status: scheduled, active, paused or ended. The campaign's status follows from them. pendingAction is a pause, start or stop a media advisor has not made yet, and otherwise null. startDate, endDate and reportUrl, the campaign's report in Frantz Business Manager, are set when the campaign is set up.

Statistics

GET /api/v2/campaigns/{campaignId}/statistics, with the scope statistics:read, gives the numbers for the campaign's own channels, in the same shape as the statistics for a job advert. Visitors who came another way, such as direct traffic, are in the job advert's statistics only. Until the campaign is set up, channels is empty.

Budget and spend

GET /api/v2/campaigns/{campaignId}/budget gives the budget and what has been spent, for the campaign and for each channel, in NOK without VAT: the same numbers media advisors see in Frantz Business Manager.

  • total is the budget. Before the campaign is set up, it is what the quote puts on the media products.
  • spent is what the advertiser has used of it. A channel Frantz has no integration with has no spend: its spent is null, and it does not count in the campaign's.
  • The numbers are not live. lastUpdated tells when Frantz last got them from the networks, at least once a day while the campaign runs.

Following your campaigns

GET /api/v2/campaigns lists your campaigns, the oldest change first. To pick up changes from a background job, send the last updatedDate you have seen as updatedSince. The list includes campaigns changed at that very time, so you will see the last one again.

  • status and externalReference narrow the list.
  • limit is the page size, from 1 to 200, and 50 when left out.
  • When nextCursor is not null, send it as cursor for the next page.
HTTP
GET /api/v2/campaigns?updatedSince=2026-10-22T09:41:12Z&limit=100 HTTP/1.1
Host: api.frantz.no
Authorization: Bearer fzat_...

Money

  • Every amount is in Norwegian kroner (NOK), excluding VAT.
  • It is what the advertiser pays. What the advertising networks charge Frantz is not shown.
  • The price of help with material comes from the agreement with Frantz of the company that orders: the advertiser itself, or your company when you order for it.