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
- Log in and go to Account → API.
- Give the key a name you'll recognise, like "Zapier" or "Webshop".
- 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:
Authorization: Bearer fiig_…Without a valid key you get 401 unauthorized. An example that lists your widgets:
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:
{
"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 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"
}
}'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 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 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 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
configreplaces the old value. unitsandlabelsmerge per key:{ "labels": { "days": "d" } }only changes the label for days.nullresets a field to its default, for example{ "cornerRadius": null }.- For a poll,
optionsis always replaced as a whole. Send the existingids when you reword an answer: they're in the vote links of sent emails and in the counts. - A widget's
typecan't change.
Make a countdown end a week later:
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 -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 -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 -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.
| Field | Default | Description |
|---|---|---|
targetAt | required | End date and time, in the time zone below: 2026-12-24T18:00. An offset works too: 2026-12-24T18:00:00+01:00. |
timezone | required | IANA time zone, for example Europe/Amsterdam or Europe/London. |
locale | nl | Language of the default labels and text: nl or en. |
units | all on | Which units to show: { "days": true, "hours": true, "minutes": true, "seconds": true }. |
labels | per language | Your own label per unit, up to 24 characters: { "days": "days" }. Empty = default label. |
width, height | 600, 200 | Size in pixels, as shown in your email. Width 100 to 600, height 50 to 300. |
background | #ffffff | Background color, or transparent. |
matteColor | #ffffff | With a transparent background: your email's background color. Edges are blended with it. |
boxColor | #18181b | Color of the boxes. |
digitColor | #ffffff | Color of the digits. |
labelColor | #d4d4d8 | Color of the labels. |
font | inter | Font: inter, montserrat, poppins, spacegrotesk, oswald, bebas, dmserif, nunito, mono. |
cornerRadius | 10 | Rounding of the boxes in percent: 0 is square, 50 fully round. |
expired | text | After 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.
| Field | Default | Description |
|---|---|---|
question | required | The question, up to 140 characters. It's text in your email, above the buttons. |
options | required | 2 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. |
locale | nl | Language of the vote page: nl or en. |
closesAt | none | Closes 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. |
timezone | Europe/Amsterdam | IANA time zone, for closesAt and the votes per day. |
resultsInEmail | true | Show the live results as an image below the buttons. Set to false if you don't want to influence readers. |
publicResults | false | A public page with the live results at fiig.io/r/{id}, to share. Not findable in Google. |
showVoteCounts | false | Also show the number of votes on that page, not only percentages. |
afterVote | results | What readers see after voting: { "type": "results" }, { "type": "message", "text": "Thanks!" } or { "type": "redirect", "url": "https://…" }. |
width | 600 | Width of the whole poll block in pixels, 240 to 600. |
background | #ffffff | Background color of the poll block. Always a color, so the question stays readable in dark mode too. |
textColor | #18181b | Color of the question and the text in the results. |
buttonColor | #9333ea | Color of the answer buttons. |
buttonTextColor | #ffffff | Text color on the buttons. |
barColor | #9333ea | Color of the bars in the results. |
trackColor | #f4f4f5 | Color behind the bars. |
font | inter | Font: inter, montserrat, poppins, spacegrotesk, oswald, bebas, dmserif, nunito, mono. |
cornerRadius | 10 | Rounding 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.
| Field | Default | Description |
|---|---|---|
goal | required | The goal, more than 0. |
current | 0 | The current value. Above the goal the bar stays full and the percentage keeps counting (112%). |
title | empty | Optional line above the bar, up to 80 characters. |
valueFormat | both | Below the bar: both (amount and percentage), amount, percent or none. |
prefix, suffix | empty | Text around every amount, like "€ " before or " kg" after. |
decimals | 0 | Number of decimals: 0, 1 or 2. |
locale | nl | Number format (7.200 or 7,200) and the word between value and goal: nl or en. |
timezone | Europe/Amsterdam | IANA time zone, for the opens per day. |
width | 600 | Width in pixels, 240 to 600. The height follows from what you show. |
background | #ffffff | Background color. Always a color, so the title and amounts stay readable in dark mode too. |
textColor | #18181b | Color of the title and the amounts. |
barColor | #9333ea | Color of the bar. |
trackColor | #f4f4f5 | Color behind the bar. |
font | inter | Font: inter, montserrat, poppins, spacegrotesk, oswald, bebas, dmserif, nunito, mono. |
cornerRadius | 10 | Rounding 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.
| Field | Default | Description |
|---|---|---|
count | 0 | The number in stock, a whole number of 0 or more. At 0 it says it's sold out. |
textBefore | empty | Text before the number, up to 40 characters, for example Only. |
textAfter | empty | Text after the number, up to 40 characters, for example left in stock. |
soldOutText | per language | Text when the count is 0. Empty: Uitverkocht or Sold out. |
lowStock | 0 | From this count on, the number gets lowColor. 0 turns this off. |
locale | nl | Number format (1.250 or 1,250) and the default sold-out text: nl or en. |
timezone | Europe/Amsterdam | IANA time zone, for the opens per day. |
align | center | Alignment: center or left. |
width | 600 | Width in pixels, 240 to 600. If the text doesn't fit, everything gets a little smaller. |
background | #ffffff | Background color. Always a color, so the text stays readable in dark mode too. |
textColor | #18181b | Color of the texts. |
numberColor | #9333ea | Color of the number and of the sold-out text. |
lowColor | #dc2626 | Color of the number when almost gone (see lowStock). |
font | inter | Font: 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 "https://api.fiig.io/v1/widgets/k7Qx2mPa9rTb/stats?days=7" \
-H "Authorization: Bearer $FIIG_API_KEY"{
"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.
{
"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:
{
"error": {
"code": "invalid_request",
"message": "Some fields are invalid. See details.",
"details": [
{ "path": "config.width", "message": "Too big: expected number to be <=600" }
]
}
}| Status | Code | Meaning |
|---|---|---|
| 401 | unauthorized | No key, or the key is wrong or revoked. |
| 404 | not_found | No widget with this id in your workspace. |
| 422 | invalid_request | A field is invalid. details lists each problem (path and message). |
| 429 | rate_limited | Too many requests. Wait the number of seconds in the Retry-After header. |
| 500 | server_error | Something went wrong on our side. Try again later. |
Limits
- Up to 120 requests per minute per key. Above that you get
429with aRetry-Afterheader. - 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
- Pick a trigger, for example a new or updated row in Google Sheets.
- Add Webhooks by Zapier as the action, with the event Custom Request.
- Method:
PATCH. URL:https://api.fiig.io/v1/widgets/followed by your widget's id. - Data: the JSON below, with the field from your trigger in place of
{{deadline}}. - Headers:
Authorizationwith the valueBearerand your key, andContent-Typewithapplication/json. - Test the step. On success you get the widget back with its new end date.
{ "config": { "targetAt": "{{deadline}}" } }Make
- Add the module HTTP → Make a request.
- URL and method as above, body type Raw, content type JSON (application/json).
- Add the header
AuthorizationwithBearerand 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.