API documentation

With the Fiig API you create, update and delete widgets from your own systems, and fetch their statistics. For example, to create a countdown for every campaign automatically, or to keep its end date in step with your planning.

Getting started

  • Base URL: https://api.fiig.io/v1. All requests go over HTTPS.
  • You send and receive JSON. With a body, send the header Content-Type: application/json.
  • The API is free during the beta.

There are four widget types: countdown, poll, progress (progress bar) and stock (stock counter). They use the same keys and endpoints, each with its own settings. Without a type you create a countdown, so existing integrations keep working. Upcoming widgets will be added the same way.

Not writing code yourself? With Zapier or Make you can use the API without programming. See Zapier and Make below.

Create an API key

  1. Log in and go to Account → API.
  2. Give the key a name you'll recognise, like "Zapier" or "Webshop".
  3. Click Create key and copy the key right away. You only see it once.
  • A key belongs to your workspace: it works for all widgets in it, including your colleagues'.
  • You can have up to 10 keys. Use one per system, so you can revoke one without affecting the rest.
  • Each key shows when it was last used.

Treat a key like a password. Only use it on a server or in a tool like Zapier, never in a website, app or email. Leaked? Revoke it under Account → API and create a new one.

Authentication

Send your key with every request in the Authorization header:

Header
Authorization: Bearer fiig_…

Without a valid key you get 401 unauthorized. An example that lists your widgets:

curl
curl https://api.fiig.io/v1/widgets \
  -H "Authorization: Bearer $FIIG_API_KEY"

Widgets

Every widget has an id: the same part as in the image URL, so k7Qx2mPa9rTb in https://fiig.email/t/k7Qx2mPa9rTb.gif. A widget looks like this:

JSON
{
  "data": {
    "id": "k7Qx2mPa9rTb",
    "type": "countdown",
    "name": "Christmas sale",
    "altText": "3 days until Christmas",
    "config": {
      "targetAt": "2026-12-24T18:00",
      "timezone": "Europe/Amsterdam",
      "width": 600,
      "height": 200,
      "...": "..."
    },
    "imageUrl": "https://fiig.email/t/k7Qx2mPa9rTb.gif",
    "embedHtml": "<img src=\"https://fiig.email/t/k7Qx2mPa9rTb.gif\" width=\"600\" height=\"200\" alt=\"3 days until Christmas\" style=\"display:block;border:0;\">",
    "dashboardUrl": "https://fiig.io/dashboard/widgets/42",
    "createdAt": "2026-10-05T14:02:11.000Z",
    "updatedAt": "2026-10-05T14:02:11.000Z"
  }
}

config always contains every setting, defaults included. embedHtml is the code you paste into your email: one <img> tag for a countdown, progress bar or stock counter; the question, answer buttons and live results for a poll. For a poll, imageUrl is the results image.

List widgets

GET/v1/widgets

Returns your widgets, most recently changed first, in pages. Use limit (1 to 100, default 50) and page (from 1). The response has data, page, totalPages and totalCount.

GET/v1/widgets/{id}

Returns one widget.

Create a widget

POST/v1/widgets

Required are name and, in config, the end date (targetAt) and time zone (timezone). Anything you leave out comes from your workspace's brand, or else from the defaults, just like in the dashboard. You get 201 with the new widget, including embedHtml.

curl
curl https://api.fiig.io/v1/widgets \
  -H "Authorization: Bearer $FIIG_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Christmas sale",
    "altText": "Christmas is almost here",
    "config": {
      "targetAt": "2026-12-24T18:00",
      "timezone": "Europe/Amsterdam"
    }
  }'
JavaScript
const response = await fetch("https://api.fiig.io/v1/widgets", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.FIIG_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    name: "Christmas sale",
    config: { targetAt: "2026-12-24T18:00", timezone: "Europe/Amsterdam" },
  }),
});
const { data: widget } = await response.json();
console.log(widget.embedHtml);

Create a poll

Send "type": "poll". Required are name and, in config, the question (question) and 2 to 5 answers (options). An answer without an id gets one automatically. All fields are under Poll settings.

curl
curl https://api.fiig.io/v1/widgets \
  -H "Authorization: Bearer $FIIG_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "type": "poll",
    "name": "Cake of the week",
    "config": {
      "question": "Which cake should we bake on Saturday?",
      "options": [
        { "label": "Apple pie" },
        { "label": "Chocolate cake" },
        { "label": "Lemon tart" }
      ],
      "closesAt": "2026-12-20T18:00",
      "timezone": "Europe/Amsterdam"
    }
  }'

Create a progress bar

Send "type": "progress". Required are name and, in config, the goal (goal). The current value (current) starts at 0 if you leave it out. All fields are under Progress bar settings.

curl
curl https://api.fiig.io/v1/widgets \
  -H "Authorization: Bearer $FIIG_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "type": "progress",
    "name": "New oven",
    "config": {
      "current": 7200,
      "goal": 10000,
      "title": "Help us buy a new oven",
      "prefix": "€ "
    }
  }'

Create a stock counter

Send "type": "stock". Only name is required; the count (count) starts at 0 if you leave it out. Without textBefore and textAfter you get "Nog 25 op voorraad" (or "Only 25 left in stock" with "locale": "en"). All fields are under Stock counter settings.

curl
curl https://api.fiig.io/v1/widgets \
  -H "Authorization: Bearer $FIIG_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "type": "stock",
    "name": "Christmas cakes",
    "config": {
      "count": 25,
      "textBefore": "Only",
      "textAfter": "Christmas cakes left",
      "lowStock": 5
    }
  }'

Update a widget

PATCH/v1/widgets/{id}

Send only what changes: name, altText and/or fields in config. The image in emails you already sent changes within two minutes.

  • A field in config replaces the old value.
  • units and labels merge per key: { "labels": { "days": "d" } } only changes the label for days.
  • null resets a field to its default, for example { "cornerRadius": null }.
  • For a poll, options is always replaced as a whole. Send the existing ids when you reword an answer: they're in the vote links of sent emails and in the counts.
  • A widget's type can't change.

Make a countdown end a week later:

curl
curl -X PATCH https://api.fiig.io/v1/widgets/k7Qx2mPa9rTb \
  -H "Authorization: Bearer $FIIG_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "config": { "targetAt": "2026-12-31T23:59" } }'

Update a progress bar's value, for example from your webshop, Zapier or Make. Send the new total, not the difference:

curl
curl -X PATCH https://api.fiig.io/v1/widgets/Rt5mW8cQz2Lp \
  -H "Authorization: Bearer $FIIG_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "config": { "current": 8350 } }'

A stock counter's count works the same way. At 0 it shows the sold-out text:

curl
curl -X PATCH https://api.fiig.io/v1/widgets/Wq3nB7xKd9Fs \
  -H "Authorization: Bearer $FIIG_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "config": { "count": 12 } }'

Delete a widget

DELETE/v1/widgets/{id}

Returns 204 without content. Note: the image also disappears from emails that were already sent.

curl
curl -X DELETE https://api.fiig.io/v1/widgets/k7Qx2mPa9rTb \
  -H "Authorization: Bearer $FIIG_API_KEY"

Countdown settings

These are the fields in config. Colors are always #rrggbb.

FieldDefaultDescription
targetAtrequiredEnd date and time, in the time zone below: 2026-12-24T18:00. An offset works too: 2026-12-24T18:00:00+01:00.
timezonerequiredIANA time zone, for example Europe/Amsterdam or Europe/London.
localenlLanguage of the default labels and text: nl or en.
unitsall onWhich units to show: { "days": true, "hours": true, "minutes": true, "seconds": true }.
labelsper languageYour own label per unit, up to 24 characters: { "days": "days" }. Empty = default label.
width, height600, 200Size in pixels, as shown in your email. Width 100 to 600, height 50 to 300.
background#ffffffBackground color, or transparent.
matteColor#ffffffWith a transparent background: your email's background color. Edges are blended with it.
boxColor#18181bColor of the boxes.
digitColor#ffffffColor of the digits.
labelColor#d4d4d8Color of the labels.
fontinterFont: inter, montserrat, poppins, spacegrotesk, oswald, bebas, dmserif, nunito, mono.
cornerRadius10Rounding of the boxes in percent: 0 is square, 50 fully round.
expiredtextAfter the end: { "type": "text", "text": "This offer has ended" } or { "type": "image", "url": "https://…" }.

Poll settings

The fields in a poll's config. Colors are always #rrggbb. Anything you leave out comes from your brand or the defaults.

FieldDefaultDescription
questionrequiredThe question, up to 140 characters. It's text in your email, above the buttons.
optionsrequired2 to 5 answers: [{ "id": "a", "label": "Apple pie" }, …]. label up to 60 characters; id 1 to 12 lowercase letters or digits, and you can leave it out for new answers.
localenlLanguage of the vote page: nl or en.
closesAtnoneCloses the poll at this moment, in the time zone below: 2026-12-20T18:00. After that, new votes don't count. Without this field the poll stays open.
timezoneEurope/AmsterdamIANA time zone, for closesAt and the votes per day.
resultsInEmailtrueShow the live results as an image below the buttons. Set to false if you don't want to influence readers.
publicResultsfalseA public page with the live results at fiig.io/r/{id}, to share. Not findable in Google.
showVoteCountsfalseAlso show the number of votes on that page, not only percentages.
afterVoteresultsWhat readers see after voting: { "type": "results" }, { "type": "message", "text": "Thanks!" } or { "type": "redirect", "url": "https://…" }.
width600Width of the whole poll block in pixels, 240 to 600.
background#ffffffBackground color of the poll block. Always a color, so the question stays readable in dark mode too.
textColor#18181bColor of the question and the text in the results.
buttonColor#9333eaColor of the answer buttons.
buttonTextColor#ffffffText color on the buttons.
barColor#9333eaColor of the bars in the results.
trackColor#f4f4f5Color behind the bars.
fontinterFont: inter, montserrat, poppins, spacegrotesk, oswald, bebas, dmserif, nunito, mono.
cornerRadius10Rounding of the buttons and bars in percent: 0 is square.

Progress bar settings

The fields in a progress bar's config. Colors are always #rrggbb. Anything you leave out comes from your brand or the defaults.

FieldDefaultDescription
goalrequiredThe goal, more than 0.
current0The current value. Above the goal the bar stays full and the percentage keeps counting (112%).
titleemptyOptional line above the bar, up to 80 characters.
valueFormatbothBelow the bar: both (amount and percentage), amount, percent or none.
prefix, suffixemptyText around every amount, like "€ " before or " kg" after.
decimals0Number of decimals: 0, 1 or 2.
localenlNumber format (7.200 or 7,200) and the word between value and goal: nl or en.
timezoneEurope/AmsterdamIANA time zone, for the opens per day.
width600Width in pixels, 240 to 600. The height follows from what you show.
background#ffffffBackground color. Always a color, so the title and amounts stay readable in dark mode too.
textColor#18181bColor of the title and the amounts.
barColor#9333eaColor of the bar.
trackColor#f4f4f5Color behind the bar.
fontinterFont: inter, montserrat, poppins, spacegrotesk, oswald, bebas, dmserif, nunito, mono.
cornerRadius10Rounding of the bar in percent: 0 is square, 50 fully round.

Stock counter settings

The fields in a stock counter's config. Colors are always #rrggbb. Anything you leave out comes from your brand or the defaults.

FieldDefaultDescription
count0The number in stock, a whole number of 0 or more. At 0 it says it's sold out.
textBeforeemptyText before the number, up to 40 characters, for example Only.
textAfteremptyText after the number, up to 40 characters, for example left in stock.
soldOutTextper languageText when the count is 0. Empty: Uitverkocht or Sold out.
lowStock0From this count on, the number gets lowColor. 0 turns this off.
localenlNumber format (1.250 or 1,250) and the default sold-out text: nl or en.
timezoneEurope/AmsterdamIANA time zone, for the opens per day.
aligncenterAlignment: center or left.
width600Width in pixels, 240 to 600. If the text doesn't fit, everything gets a little smaller.
background#ffffffBackground color. Always a color, so the text stays readable in dark mode too.
textColor#18181bColor of the texts.
numberColor#9333eaColor of the number and of the sold-out text.
lowColor#dc2626Color of the number when almost gone (see lowStock).
fontinterFont: inter, montserrat, poppins, spacegrotesk, oswald, bebas, dmserif, nunito, mono.

Statistics

GET/v1/widgets/{id}/stats

Total opens and opens per day, in the widget's time zone. Use days (1 to 366, default 30) to choose how many days back, today included. Test emails and builder previews don't count.

curl
curl "https://api.fiig.io/v1/widgets/k7Qx2mPa9rTb/stats?days=7" \
  -H "Authorization: Bearer $FIIG_API_KEY"
JSON
{
  "data": {
    "id": "k7Qx2mPa9rTb",
    "timezone": "Europe/Amsterdam",
    "totalOpens": 18452,
    "days": [
      { "date": "2026-12-18", "opens": 0 },
      { "date": "2026-12-19", "opens": 9210 },
      { "date": "2026-12-20", "opens": 4022 }
    ]
  }
}

A poll also gets votes: the total, the results per answer (in the poll's order, with whole percentages) and the votes per day. For a poll, totalOpens counts how often the results image was loaded. Votes from test emails and the builder preview don't count.

JSON
{
  "data": {
    "id": "Pq8sV2nLx4Ke",
    "timezone": "Europe/Amsterdam",
    "totalOpens": 6120,
    "days": [ … ],
    "votes": {
      "total": 412,
      "results": [
        { "id": "a", "label": "Apple pie", "votes": 231, "percent": 56 },
        { "id": "b", "label": "Chocolate cake", "votes": 124, "percent": 30 },
        { "id": "c", "label": "Lemon tart", "votes": 57, "percent": 14 }
      ],
      "days": [
        { "date": "2026-12-18", "votes": 0 },
        { "date": "2026-12-19", "votes": 301 },
        { "date": "2026-12-20", "votes": 111 }
      ]
    }
  }
}

Errors

Errors always have the same format:

JSON
{
  "error": {
    "code": "invalid_request",
    "message": "Some fields are invalid. See details.",
    "details": [
      { "path": "config.width", "message": "Too big: expected number to be <=600" }
    ]
  }
}
StatusCodeMeaning
401unauthorizedNo key, or the key is wrong or revoked.
404not_foundNo widget with this id in your workspace.
422invalid_requestA field is invalid. details lists each problem (path and message).
429rate_limitedToo many requests. Wait the number of seconds in the Retry-After header.
500server_errorSomething went wrong on our side. Try again later.

Limits

  • Up to 120 requests per minute per key. Above that you get 429 with a Retry-After header.
  • Up to 10 keys per workspace.
  • The API is meant for servers and tools. Requests from a browser aren't supported, so your key can't leak.

Zapier and Make

Connect Fiig to other apps without programming. An example: move your countdown's end date when the deadline in your planning changes.

Zapier

  1. Pick a trigger, for example a new or updated row in Google Sheets.
  2. Add Webhooks by Zapier as the action, with the event Custom Request.
  3. Method: PATCH. URL: https://api.fiig.io/v1/widgets/ followed by your widget's id.
  4. Data: the JSON below, with the field from your trigger in place of {{deadline}}.
  5. Headers: Authorization with the value Bearer and your key, and Content-Type with application/json.
  6. Test the step. On success you get the widget back with its new end date.
Data
{ "config": { "targetAt": "{{deadline}}" } }

Make

  1. Add the module HTTP → Make a request.
  2. URL and method as above, body type Raw, content type JSON (application/json).
  3. Add the header Authorization with Bearer and your key, and put the JSON in Request content.

Send the end date as 2026-12-24T18:00 (no seconds, no time zone): the widget's time zone then decides when it ends.

OpenAPI

A complete, machine-readable description of the API is at https://api.fiig.io/v1/openapi.json. Import it into Postman or Insomnia, for example, to try every request right away.