# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
This site has an agentic ask interface you may use to query the documentation dynamically by asking a question.

Perform an HTTP GET request on the following URL with the `ask` and `goal` query parameters:

```
GET https://docs.ipaper.io/index.md?ask=<question>&goal=<user_goal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `automate deployments from our CI pipeline` lets GitBook tailor the answer to that use case.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

---

# iPaper Technical Documentation

Welcome to the iPaper technical documentation.

Here you’ll find everything tech related, such as how to consume our API and how to implement various integrations, etc.

If you find anything missing, please contact our customer care at <support@ipaper.io>


# Google Analytics

Learn how to implement Google Analytics tracking in your Display

## Overview

Google Analytics tracking is configurable on a license-level: the same Google Analytics Tracking ID will be applied to all your Display instances in the same license, once configured.

## Events

All events are tracked to the **iPaper Display** category in the following format:

```javascript
gtag('event', [EVENT NAME], {
	event_category: 'iPaper Display',
	event_action: [ACTION],
	event_label: [LABEL],
	non_interaction: [IS NON INTERACTIVE]
});
```

{% hint style="info" %}
**Note:**

Google Analytics 4 (GA4) does not by default collect event categories, so event names in GA4 have ‘ipd\_’ prepended for iPaper Display events
{% endhint %}

{% hint style="info" %}
**Note:**

When using Google Analytics 4 (GA4), the contents of event\_label are also sent as individual event parameters prepended with ‘ip\_’
{% endhint %}

### Overview <a href="#overview-1" id="overview-1"></a>

{% hint style="info" %}
**Note:**

Dependent on whether you are using Universal Analytics (UA) or Google Analytics 4 (GA4) the event names will be slightly different
{% endhint %}

<table><thead><tr><th width="127">UA event name</th><th width="130">GA4 event name</th><th width="114">Interactive</th><th width="237">Data</th><th>Comments</th></tr></thead><tbody><tr><td><strong>Instance</strong></td><td></td><td></td><td></td><td></td></tr><tr><td>Load</td><td>ipd_load</td><td>No</td><td><p></p><pre class="language-javascript"><code class="lang-javascript">{
  Url: [DISPLAY_URL]
}
</code></pre></td><td></td></tr><tr><td>Time Spent</td><td>ipd_time_spent</td><td>No</td><td><p></p><pre class="language-javascript"><code class="lang-javascript">{
  Duration: [DURATION_IN_SECONDS]
}
</code></pre></td><td></td></tr><tr><td><strong>Button entity</strong></td><td></td><td></td><td></td><td></td></tr><tr><td>Button Click</td><td>ipd_button_click</td><td>Yes</td><td><p></p><pre class="language-javascript"><code class="lang-javascript">{
  ActionUrl: [BUTTON_ENTITY_ACTION_URL]
}
</code></pre></td><td></td></tr><tr><td><strong>Image entity</strong></td><td></td><td></td><td></td><td></td></tr><tr><td>Image Click</td><td>ipd_image_click</td><td>Yes</td><td><p></p><pre class="language-javascript"><code class="lang-javascript">{
  ActionUrl: [IMAGE_ENTITY_ACTION_URL],
  AssetUrl: [IMAGE_ENTITY_ASSET_URL]
}
</code></pre></td><td></td></tr><tr><td><strong>Product entity</strong></td><td></td><td></td><td></td><td></td></tr><tr><td>Product Click</td><td>ipd_product_click</td><td>Yes</td><td><p></p><pre class="language-javascript"><code class="lang-javascript">{
  Id: [PRODUCT_ENTITY_PRODUCT_ID],
  ActionUrl: [PRODUCT_ENTITY_PRODUCT_URL]
}
</code></pre></td><td></td></tr><tr><td><strong>Instance</strong></td><td></td><td></td><td></td><td></td></tr><tr><td>Video Click</td><td>ipd_video_click</td><td>Yes</td><td><p></p><pre class="language-javascript"><code class="lang-javascript">{
  ActionUrl: [VIDEO_ENTITY_ACTION_URL],
  AssetUrl: [VIDEO_ENTITY_ASSET_URL]
}
</code></pre></td><td>Only available for autoplaying video. <code>ActionUrl</code> is only present if the entity has an action assigned to it.</td></tr><tr><td>Video Start</td><td>ipd_video_start</td><td>Yes</td><td><p></p><pre class="language-javascript"><code class="lang-javascript">{
  ActionUrl: [VIDEO_ENTITY_ACTION_URL],
  AssetUrl: [VIDEO_ENTITY_ASSET_URL]
}
</code></pre></td><td>Only available for non-autoplaying video. Fires once per video entity, when a user manually plays video. <code>ActionUrl</code> is only present if the entity has an action assigned to it.</td></tr><tr><td>Video Played 25 Percent</td><td>ipd_video_played_25_percent</td><td>Yes</td><td><p></p><pre class="language-javascript"><code class="lang-javascript">{
  ActionUrl: [VIDEO_ENTITY_ACTION_URL],
  AssetUrl: [VIDEO_ENTITY_ASSET_URL]
}
</code></pre></td><td>Only available for non-autoplaying video. Fires once per video entity, once the timestamp of a video exceeds the stated percentage of its total duration. <code>ActionUrl</code> is only present if the entity has an action assigned to it.</td></tr><tr><td>Video Played 50 Percent</td><td>ipd_video_played_50_percent</td><td>Yes</td><td><p></p><pre class="language-javascript"><code class="lang-javascript">{
  ActionUrl: [VIDEO_ENTITY_ACTION_URL],
  AssetUrl: [VIDEO_ENTITY_ASSET_URL]
}
</code></pre></td><td>Only available for non-autoplaying video. Fires once per video entity, once the timestamp of a video exceeds the stated percentage of its total duration. <code>ActionUrl</code> is only present if the entity has an action assigned to it.</td></tr><tr><td>Video Played 75 Percent</td><td>ipd_video_played_75_percent</td><td>Yes</td><td><p></p><pre class="language-javascript"><code class="lang-javascript">{
  ActionUrl: [VIDEO_ENTITY_ACTION_URL],
  AssetUrl: [VIDEO_ENTITY_ASSET_URL]
}
</code></pre></td><td>Only available for non-autoplaying video. Fires once per video entity, once the timestamp of a video exceeds the stated percentage of its total duration. <code>ActionUrl</code> is only present if the entity has an action assigned to it.</td></tr><tr><td>Video Played 100 Percent</td><td>ipd_video_played_100_percent</td><td>Yes</td><td><p></p><pre class="language-javascript"><code class="lang-javascript">{
  ActionUrl: [VIDEO_ENTITY_ACTION_URL],
  AssetUrl: [VIDEO_ENTITY_ASSET_URL]
}
</code></pre></td><td>Only available for non-autoplaying video. Fires once per video entity, once the timestamp of a video exceeds the stated percentage of its total duration. <code>ActionUrl</code> is only present if the entity has an action assigned to it.</td></tr></tbody></table>

### Example <a href="#example" id="example"></a>

For example, when somebody clicks on a button in your Display, we will track it as such via the “Button Click” event:

**Universal Analytics (UA):**

```javascript
gtag('event', 'Button Click', {
	event_category: 'iPaper Display',
	event_action: 'Button Click',
	event_label: 'Url: https://wwww.ipaper.io/',
	non_interaction: true
});
```

**Google Analytics 4 (GA4):**

```javascript
gtag('event', 'ipd_button_click', {
	event_category: 'iPaper Display',
	event_action: 'Button Click',
	event_label: 'Url: https://wwww.ipaper.io/',
	non_interaction: true,
	ip_url: 'https://wwww.ipaper.io/'
});
```

### Interactive flag <a href="#interactive-flag" id="interactive-flag"></a>

There are some events that we want to mark as non-interactive (or [“non-interaction events”](https://developers.google.com/analytics/devguides/collection/analyticsjs/events?hl=da#non-interaction_events) by Google Analytics’ definition). This will mean that even if these events are fired, the session will be considered as bounced if the user does not further interact with the page.

### Event labels

The data format below is represented in JSON. It is converted to a string for the `eventLabel` object using the following convention:

* Key names are PascalCased
* Key-value pairs are separated by the pipe character `|`

Example:

```json
{
  Id: [PRODUCT_ENTITY_PRODUCT_ID],
  Url: [PRODUCT_ENTITY_PRODUCT_URL]
}
```

…is converted into:

`Id: [PRODUCT_ENTITY_PRODUCT_ID] | Url: [PRODUCT_ENTITY_PRODUCT_URL]`


# Quick Start

The v1 Display JavaScript API allows for streamlined interaction a Display instance

## Overview

The v1 Display JavaScript API is used for client webpages to show and interact with iPaper Display instances. Display instances may be directly associated with a given URL by means of pattern matching of the current page URL against a pre-defined pattern in the call-to-action (CTA) button URL value provided.

### Display instance

A Display instance, also known as a “Display”, can be built from the iPaper Admin, if you have the Display module enabled. It is 400px wide and has an arbitrary height, depending on the amount of content in the instance.

### The call-to-action (CTA) button

For every Display instance, a call-to-action (CTA) button is associated with it. The instance and the CTA button can be toggled individually. In a typical zero-configuration ground state:

* if a Display instance is associated with a given URL, the CTA button will be shown on page load. Clicking on the button will reveal the Display instance, which will animate into the viewport from the left or right side offscreen. The Display API can be used to show/hide the button and the Display instance programmatically.
* if a given URL has no Display instance associated with it, no CTA button or Display instance will be shown. However, you can load a Display instance arbitrarily using this API.

The Display Javascript API [exposes magic commands](/display-javascript-api/commands) and [emits a custom event upon instantiation](/display-javascript-api/events).

## Setup

A Display instance and its CTA button can only be shown on pages that has the Display JavaScript API embedded. The Display API embed script will look something like this, with `<ApiSecret>` being substitued with partner and license-dependent configurations:

```javascript
<!-- Start of Async iPaper Display API script -->
<script>
(function(i,P,a,p,e,r){if(i.getElementById(a=a+'-'+e))return;
r=i.querySelector(P).parentNode.appendChild(i.createElement(P));
r.id=a;r.async=1;r.src=p+'/'+e+'.js'})
(document,'script','ipaper-display-api','//display.ipaper.io/api/v1','<ApiSecret>');
</script>
<!-- End of Async iPaper Display API script -->
```

{% hint style="info" %}
**Note:**

`<ApiSecret>` in the snippet above will be substituted with partner and license-dependent configurations and be automatically populated, when you retrieve the API script from the admin.
{% endhint %}


# Commands

Commands are instructions issued by the page to the Display JavaScript API. Commands can be invoked by calling magic methods, which can be accessed directly from the API instance.

## Overview

Commands are issued by your page, using magic methods exposed by the Display API instance. As these magic commands are attached to the Display API instance, it is imperative that you ensure the Display API is successfully instantiated. You can either:

1. check if the API is present upon at invocation

   ```javascript
    // Example 1
    if (window.iPaperDisplayApi) {
        window.iPaperDisplayApi.show();
    }

    // Example 2
    window.iPaperDisplayApi && window.iPaperDisplayApi.show();
   ```
2. or listen to a custom event that signals that the API is ready to use.

   ```javascript
    document.addEventListener('iPaperDisplayApiReady', function () {
        // At the point in time, window.iPaperDisplayApi is available in the global scope
        window.iPaperDisplayApi.show();
    });
   ```

   See [Events page for further information](/display-javascript-api/events).

## Display instance

A Display instance will be shown in a full viewport height `<iframe>` element that will enter enter the viewport from the left or right of the screen, depending on the settings configured for the specific instance.

### `show`

This command can be used to show:

1. A Display instance that is already associated with a URL, by means of URL pattern matching against the CTA button URL pattern configured for a particular instance, or
2. An arbitrary Display instance

### **1. Showing a Display instance associated with a URL**

If the current page matches the CTA button URL pattern, you can simply use the `show()` magic command without any arguments to show a Display instance associated with the URL:

```javascript
/*
 * Show the Display instance that is associated with a particular URL
 * NOTE: This only works if the current page already has a Display instance associated with it.
 */
window.iPaperDisplayApi.show();
```

### **2. Showing an arbitrary Display instance**

For the second scenario is useful when you have the Display JS API embedded on your page, but has no Display instance associated with it because it does not match the CTA button URL pattern. By invoking `show(<guid>)`, where `guid` refers to the GUID of your Display instance of choice, you can show any arbitrary Display instance associated with your license on that page. Your GUID will be in the format of `xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx`.

```javascript
/*
 * Show an arbitrary Display instance
 * NOTE: The GUID of your Display instance can be inferred from its canonical URL in the following format:
 * - https://display.ipaper.io/<license>/<guid>
 */
window.iPaperDisplayApi.show(<guid>);
```

### `hide`

The `hide()` magic command does not accept any arguments, and simply hides the Display instance away:

```javascript
window.iPaperDisplayApi.hide();
```

## Display call-to-action button

### `showButton`

Similar to the `show()` magic command, this command can be used to show:

1. A Display call-to-action button that is already associated with a URL, by means of URL pattern matching against the CTA button URL pattern configured for a particular instance, or
2. An arbitrary Display call-to-action button

### **1. Showing a Display call-to-action button associated with a URL**

```javascript
/*
 * Show the Display call-to-action button that is associated with a particular URL
 * NOTE: This only works if the current page already has a Display instance associated with it.
 */ 
window.iPaperDisplayApi.showButton();
```

### **2. Showing an arbitrary Display call-to-action button**

```javascript
/*
 * Show an arbitrary Display call-to-action button
 * NOTE: The GUID of your Display instance can be inferred from its canonical URL in the following format:
 * - https://display.ipaper.io/<license>/<guid>
 */
window.iPaperDisplayApi.showButton(<guid>);
```

### `hideButton`

The `hide()` magic command does not accept any arguments, and simply hides the Display call-to-action button away:

```javascript
window.iPaperDisplayApi.hideButton();
```


# Events

Display JavaScript API emits custom events that can be listened to on the document root

## Overview

The Display JavaScript API emits custom events to the document root so that you can keep track of changes to the Display API instance.

### `iPaperDisplayApiReady`

This event is emitted when the Display JavaScript API has successfully instantiated and populates the global scope with the `iPaperDisplayApi` object. At this point of time, all magic commands for the Display JavaScript API is accessible.

```javascript
document.addEventListener('iPaperDisplayApiReady', function () {
    // At this point in time, window.iPaperDisplayApi is available in the global scope
    window.iPaperDisplayApi.show();
});
```

### `iPaperDisplayInstanceReady`

This event is emitted when the Display JavaScript API has successfully loaded a Display instance and the instsance is now visible and ready to be interacted with. At this point, the user should be able to interact (scroll, click, etc.) with the Display instance shown on the side.

```javascript
document.addEventListener('iPaperDisplayInstanceReady', function () {
    // At this point in time, the Display Instance is ready to be interacted with
});
```

{% hint style="info" %}
**Note:**

Due to the nature of the UI/UX of Display, the `iPaperDisplayInstanceReady` event is only fired if your user is originating from a desktop or tablet device. Mobile devices are redirected to the actual URL of the Display, where interaction with the API is not possible.
{% endhint %}


# Adobe Analytics events

How event tracking works on your flipbook when you add Adobe Analytics integration.

## Adding Adobe Analytics integration

Adobe Analytics Integration can be enabled by copying the [launch embed code](https://docs.adobe.com/content/help/en/core-services-learn/implementing-in-websites-with-launch/configure-launch/launch-add-embed.html) and pasting in into your flipbook integration settings.

## Events

All events are tracked according to the [launch object reference published by Adobe Analytics](https://docs.adobe.com/content/help/en/launch/using/reference/client-side-info/launch-object-reference.html).

```javascript
window._satellite.track(identifier, detail);
```

### Overview

<table><thead><tr><th>Identifier</th><th>Detail</th><th>Comment</th></tr></thead><tbody><tr><td>External Link Click</td><td><pre class="language-javascript"><code class="lang-javascript">{
  HoverText: [LINK_HOVER_TEXT],
  Url: [EXTERNAL_URL],
  Page: [PAGE_NUMBER]
}
</code></pre></td><td></td></tr><tr><td>Open Display</td><td><pre class="language-javascript"><code class="lang-javascript">{
   Guid: [DISPLAY_GUID],
   Page: [PAGE_NUMBER]
}
</code></pre></td><td></td></tr><tr><td>Load</td><td><pre class="language-javascript"><code class="lang-javascript">{
  Url: [FLIPBOOK_URL]
}
</code></pre></td><td>Flipbook URL is simply read from <code>window.location.href</code></td></tr><tr><td>PDF Download</td><td><code>{}</code></td><td></td></tr><tr><td>Popup Image Click</td><td><pre class="language-javascript"><code class="lang-javascript">{
  HoverText: [LINK_HOVER_TEXT],
  Url: [IMAGE_URL],
  Page: [PAGE_NUMBER]
}
</code></pre></td><td></td></tr><tr><td>Popup Image Gallery Click</td><td><pre class="language-javascript"><code class="lang-javascript">{
  HoverText: [LINK_HOVER_TEXT],
  Page: [PAGE_NUMBER]
}
</code></pre></td><td></td></tr><tr><td>Popup Media Gallery Click</td><td><pre class="language-javascript"><code class="lang-javascript">{
  HoverText: [LINK_HOVER_TEXT],
  Page: [PAGE_NUMBER]
}
</code></pre></td><td></td></tr><tr><td>Popup Content Click</td><td><pre class="language-javascript"><code class="lang-javascript">{
  HoverText: [LINK_HOVER_TEXT],
  Page: [PAGE_NUMBER]
}
</code></pre></td><td></td></tr><tr><td>Popup Frame Click</td><td><pre class="language-javascript"><code class="lang-javascript">{
  HoverText: [LINK_HOVER_TEXT],
  Url: [FRAME_URL],
  Page: [PAGE_NUMBER]
}
</code></pre></td><td></td></tr><tr><td>Product Added to Basket</td><td><pre class="language-javascript"><code class="lang-javascript">{
  Id: [PRODUCT_ID],
  Name: [PRODUCT_NAME],
  Page: [PAGE_NUMBER]
}
</code></pre></td><td></td></tr><tr><td>News ticker External Link Click</td><td><pre class="language-javascript"><code class="lang-javascript">{
  Url: [EXTERNAL_URL]
}
</code></pre></td><td></td></tr><tr><td>Search</td><td><pre class="language-javascript"><code class="lang-javascript">{
  Term: [SEARCH_TERM]
}
</code></pre></td><td></td></tr><tr><td>Shop Checkout</td><td><pre class="language-javascript"><code class="lang-javascript">{
  NumberOfProducts: [NO_OF_PRODUCT_IN_CART],
  CheckoutType: [CHECKOUT_TYPE],
  BasketValue: [TOTAL_BASKET_VALUE]
}
</code></pre></td><td>Possible <code>CheckoutType</code> values are <code>Email</code>, <code>ShareEmail</code>, <code>Checkout</code>, or <code>Print</code>.</td></tr><tr><td>Video Play</td><td><pre class="language-javascript"><code class="lang-javascript">{
  Url: [VIDEO_URL],
  VideoType: [VIDEO_TYPE],
  Page: [PAGE_NUMBER]
}
</code></pre></td><td><strong>Only the first play is registered.</strong> Possible <code>VideoType</code> values are <code>Video</code>, <code>YouTube</code>, or <code>Vimeo</code>.</td></tr><tr><td>Amount Selector Modal Open</td><td><pre class="language-javascript"><code class="lang-javascript">{
  Id: [PRODUCT_ID],
  Page: [PAGE_NUMBER]
}
</code></pre></td><td></td></tr><tr><td>Products on Page Modal Open</td><td><pre class="language-javascript"><code class="lang-javascript">{
  Page: [PAGE_NUMBER]
}
</code></pre></td><td></td></tr><tr><td>Shop Detail Item Modal Open</td><td><pre class="language-javascript"><code class="lang-javascript">{
  Id: [PRODUCT_ID],
  Page: [PAGE_NUMBER]
}
</code></pre></td><td></td></tr><tr><td>Shop Multivariant Modal Open</td><td><pre class="language-javascript"><code class="lang-javascript">{
  Page: [PAGE_NUMBER]
}
</code></pre></td><td></td></tr></tbody></table>

{% hint style="info" %}
**Note:**

The values in `[]` will be replaced with context specific value before sending the event.
{% endhint %}

### Example

For example, when somebody searches for “Cute puppies” in your flipbook, we will track it as such via the “Search” event:

```javascript
window._satellite.track('Search', {
	'Term': 'Cute puppies'
});
```

## Page views

Pageviews are tracked as a separate event by its own:

```javascript
window._satellite.track('pageview', {
	page: [PAGE_URL],
	title: [PAGE_TITLE]
})
```


# Google Analytics / GTM events

How event tracking works on your flipbook when you add Google Analytics or Google Tag Manager integration.

## Google Analytics

We track **Pageviews** (both initial load and if end user pages the flipbook) in Google Analytics. On top of that, we also track various **Events** across our flipbooks. Take a look at the list below for a full list of events.

### Events

All events are tracked to the **iPaper events** category in the following formats:

**Universal Analytics (UA):**

```javascript
window.ga(tracker.get('name') + '.send', {
	hitType: 'event',
	eventCategory: 'iPaper events',
	eventAction: [ACTION],
	eventLabel: [LABEL]
});
```

**Google Analytics 4 (GA4):**

```javascript
window.gtag('event', [EVENT NAME], {
	event_category: 'iPaper events',
	event_label: [LABEL],
	event_title: [ACTION],
	event_action: [ACTION]
});
```

{% hint style="info" %}
**Note:**

Google Analytics 4 (GA4) does not by default collect event categories, so event names in GA4 have ‘ipf\_’ prepended for iPaper Flipbook events
{% endhint %}

{% hint style="info" %}
**Note:**

When using Google Analytics 4 (GA4), the contents of event\_label are also sent as individual event parameters prepended with ‘ip\_’<br>
{% endhint %}

#### **Overview**

{% hint style="info" %}
**Note:**

Dependent on whether you are using Universal Analytics (UA) or Google Analytics 4 (GA4) the event names will be slightly different
{% endhint %}

<table><thead><tr><th width="245">Event name</th><th>Data</th><th>Comment</th></tr></thead><tbody><tr><td>ipaperEvent_externalLinkClick</td><td><pre class="language-javascript"><code class="lang-javascript">{
  HoverText: [LINK_HOVER_TEXT],
  Url: [EXTERNAL_URL],
  Page: [PAGE_NUMBER]
}
</code></pre></td><td></td></tr><tr><td>ipaperEvent_openDisplay</td><td><pre class="language-javascript"><code class="lang-javascript">{
  Guid: [DISPLA_GUID],
  Page: [PAGE_NUMBER]
}
</code></pre></td><td></td></tr><tr><td>ipaperEvent_load</td><td><pre class="language-javascript"><code class="lang-javascript">{java
  Url: [FLIPBOOK_URL]
}
</code></pre></td><td>Flipbook URL is simply read from <code>window.location.href</code></td></tr><tr><td>ipaperEvent_PDFDownload</td><td><code>{}</code></td><td></td></tr><tr><td>ipaperEvent_leadgenPopupOpen</td><td><pre class="language-javascript"><code class="lang-javascript">{
  Name: [POPUP_NAME],
  Url: [CURRENT_URL]
}
</code></pre></td><td></td></tr><tr><td>ipaperEvent_leadgenPopupConversion</td><td><pre class="language-javascript"><code class="lang-javascript">{
  Name: [POPUP_NAME],
  Url: [CURRENT_URL]
}
</code></pre></td><td></td></tr><tr><td>ipaperEvent_leadgenPopupConversionByUrlClick</td><td><pre class="language-javascript"><code class="lang-javascript">{
  Name: [POPUP_NAME],
  Url: [CURRENT_URL]
}
</code></pre></td><td></td></tr><tr><td>ipaperEvent_popupImageClick</td><td><pre class="language-javascript"><code class="lang-javascript">{
  HoverText: [LINK_HOVER_TEXT],
  Url: [IMAGE_URL],
  Page: [PAGE_NUMBER]
}
</code></pre></td><td></td></tr><tr><td>ipaperEvent_popupGalleryImageClick</td><td><pre class="language-javascript"><code class="lang-javascript">{
  HoverText: [LINK_HOVER_TEXT],
  Page: [PAGE_NUMBER]
}
</code></pre></td><td></td></tr><tr><td>ipaperEvent_popupMediaGalleryClick</td><td><pre class="language-javascript"><code class="lang-javascript">{
  HoverText: [LINK_HOVER_TEXT],
  Page: [PAGE_NUMBER]
}
</code></pre></td><td></td></tr><tr><td>ipaperEvent_popupContentClick</td><td><pre class="language-javascript"><code class="lang-javascript">{
  HoverText: [LINK_HOVER_TEXT],
  Page: [PAGE_NUMBER]
}
</code></pre></td><td></td></tr><tr><td>ipaperEvent_popupFrameClick</td><td><pre class="language-javascript"><code class="lang-javascript">{
  HoverText: [LINK_HOVER_TEXT],
  Url: [FRAME_URL],
  Page: [PAGE_NUMBER]
}
</code></pre></td><td></td></tr><tr><td>ipaperEvent_productAddedToCart</td><td><pre class="language-javascript"><code class="lang-javascript">{
  Id: [PRODUCT_ID],
  Name: [PRODUCT_NAME],
  Page: [PAGE_NUMBER]
}
</code></pre></td><td></td></tr><tr><td>ipaperEvent_newsTickerExternalLinkClick</td><td><pre class="language-javascript"><code class="lang-javascript">{
  Url: [EXTERNAL_URL]
}
</code></pre></td><td></td></tr><tr><td>ipaperEvent_search</td><td><pre class="language-javascript"><code class="lang-javascript">{
  Term: [SEARCH_TERM]
}
</code></pre></td><td></td></tr><tr><td>ipaperEvent_shopCheckout</td><td><pre class="language-javascript"><code class="lang-javascript">{
  NumberOfProducts: [NO_OF_PRODUCT_IN_CART],
  CheckoutType: [CHECKOUT_TYPE],
  BasketValue: [TOTAL_BASKET_VALUE]
}
</code></pre></td><td>Possible <code>CheckoutType</code> values are <code>Email</code>, <code>ShareEmail</code>, <code>Checkout</code>, or <code>Print</code>.</td></tr><tr><td>ipaperEvent_videoPlay</td><td><pre class="language-javascript"><code class="lang-javascript">{
  Url: [VIDEO_URL],
  VideoType: [VIDEO_TYPE],
  Page: [PAGE_NUMBER]
}
</code></pre></td><td><strong>Only the first play is registered.</strong> Possible <code>VideoType</code> values are <code>Video</code>, <code>YouTube</code>, or <code>Vimeo</code>.</td></tr><tr><td>ipaperEvent_amountSelectorModalOpen</td><td><pre class="language-javascript"><code class="lang-javascript">{
  Id: [PRODUCT_ID],
  Page: [PAGE_NUMBER]
}
</code></pre></td><td></td></tr><tr><td>ipaperEvent_productsOnPageModalOpen</td><td><pre class="language-javascript"><code class="lang-javascript">{
  Page: [PAGE_NUMBER]
}
</code></pre></td><td></td></tr><tr><td>ipaperEvent_shopDetailItemModalOpen</td><td><pre class="language-javascript"><code class="lang-javascript">{
  Id: [PRODUCT_ID],
  Page: [PAGE_NUMBER]
}
</code></pre></td><td></td></tr><tr><td>ipaperEvent_shopMultivariantModalOpen</td><td><pre class="language-javascript"><code class="lang-javascript">{
  Page: [PAGE_NUMBER]
}
</code></pre></td><td></td></tr></tbody></table>

{% hint style="info" %}
**Note:**

The values in `[]` will be replaced with context specific value before sending the event.
{% endhint %}

#### **Example**

For example, when somebody searches for “Cute puppies” in your flipbook, we will track it as such via the “Search” event:

**Universal Analytics (UA):**

```javascript
window.ga(tracker.get('name') + '.send', {
	hitType: 'event',
	eventCategory: 'iPaper events',
	eventAction: 'Search',
	eventLabel: 'Term: Cute puppies'
});
```

**Google Analytics 4 (GA4):**

```javascript
window.gtag('event', 'ipf_search', {
	event_category: 'iPaper events',
	event_label: 'Term: Cute puppies',
	event_title: 'Search',
	event_action: 'Search',
	ip_term: 'Cute puppies'
});
```

If you need help setting up Google Analytics on your iPaper account, have a look at our [Integrate Google Analytics With iPaper Google Analytics Tracking](https://help.ipaper.io/en/articles/1857330-google-analytics-part-one-how-to-integrate-and-the-benefits-of-using-ga-with-ipaper) guide.<br>

## Google Tag Manager <a href="#google-tag-manager" id="google-tag-manager"></a>

Similar to Google Analytics, we track **Pageviews** (both initial load and if end user pages the flipbook) in Google Tag Manager. Pageviews on GTM are tracked as such:

```javascript
window.dataLayer.push({
	event: 'virtualPageview',
	virtualPageUrl: [FLIPBOOK_PAGE_URL],
	virtualPageTitle: [FLIPBOOK_PAGE_TITLE],
});
```

On top of that, we also track various **Events** across our flipbooks.

{% hint style="info" %}
**Note:**

When using Google Tag Manager to track to Google Analytics, it’s important that you haven’t also defined an [Analytics Tracking ID](https://help.ipaper.io/en/articles/1857330-google-analytics-part-one-how-to-integrate-and-the-benefits-of-using-ga-with-ipaper), since this will result in pageviews and events getting tracking twice or more. If Google Tag Manager is not used to track to Google Analytics, it’s okay to include both tags, but it’s adviced to stick with one or the other to keep your tracking as simple as possible.<br>
{% endhint %}

### Events <a href="#events-1" id="events-1"></a>

For all events we support, we’ll push the raw event data and the Category, Action and Label that we’d push to Google Analytics for the similar event.

For the events below we will push to the data layer in the following format

```javascript
window.dataLayer.push({
	'event': [EVENT_NAME],
	'ipaperEventParams': [EVENT_DATA],
	'ipaperEventCategory': [GOOGLE_ANALYTICS_EVENT_CATEGORY],
	'ipaperEventAction': [GOOGLE_ANALYTICS_EVENT_ACTION],
	'ipaperEventLabel': [GOOGLE_ANALYTICS_EVENT_LABEL]
});
```

#### **Overview**

<table><thead><tr><th>Event name</th><th>Data</th><th>Comment</th></tr></thead><tbody><tr><td>ipaperEvent_externalLinkClick</td><td><pre class="language-javascript"><code class="lang-javascript">{
  HoverText: [LINK_HOVER_TEXT],
  Url: [EXTERNAL_URL],
  Page: [PAGE_NUMBER]
}
</code></pre></td><td></td></tr><tr><td>ipaperEvent_openDisplay</td><td><pre class="language-javascript"><code class="lang-javascript">{
  Guid: [DISPLA_GUID],
  Page: [PAGE_NUMBER]
}
</code></pre></td><td></td></tr><tr><td>ipaperEvent_load</td><td><pre class="language-javascript"><code class="lang-javascript">{
  Url: [FLIPBOOK_URL]
}
</code></pre></td><td>Flipbook URL is simply read from <code>window.location.href</code></td></tr><tr><td>ipaperEvent_PDFDownload</td><td><code>{}</code></td><td></td></tr><tr><td>ipaperEvent_leadgenPopupOpen</td><td><pre class="language-javascript"><code class="lang-javascript">{
  Name: [POPUP_NAME],
  Url: [CURRENT_URL]
}
</code></pre></td><td></td></tr><tr><td>ipaperEvent_leadgenPopupConversion</td><td><pre class="language-javascript"><code class="lang-javascript">{
  Name: [POPUP_NAME],
  Url: [CURRENT_URL]
}
</code></pre></td><td></td></tr><tr><td>ipaperEvent_leadgenPopupConversionByUrlClick</td><td><pre class="language-javascript"><code class="lang-javascript">{
  Name: [POPUP_NAME],
  Url: [CURRENT_URL]
}
</code></pre></td><td></td></tr><tr><td>ipaperEvent_popupImageClick</td><td><pre class="language-javascript"><code class="lang-javascript">{
  HoverText: [LINK_HOVER_TEXT],
  Url: [IMAGE_URL],
  Page: [PAGE_NUMBER]
}
</code></pre></td><td></td></tr><tr><td>ipaperEvent_popupGalleryImageClick</td><td><pre class="language-javascript"><code class="lang-javascript">{
  HoverText: [LINK_HOVER_TEXT],
  Page: [PAGE_NUMBER]
}
</code></pre></td><td></td></tr><tr><td>ipaperEvent_popupMediaGalleryClick</td><td><pre class="language-javascript"><code class="lang-javascript">{
  HoverText: [LINK_HOVER_TEXT],
  Page: [PAGE_NUMBER]
}
</code></pre></td><td></td></tr><tr><td>ipaperEvent_popupContentClick</td><td><pre class="language-javascript"><code class="lang-javascript">{
  HoverText: [LINK_HOVER_TEXT],
  Page: [PAGE_NUMBER]
}
</code></pre></td><td></td></tr><tr><td>ipaperEvent_popupFrameClick</td><td><pre class="language-javascript"><code class="lang-javascript">{
  HoverText: [LINK_HOVER_TEXT],
  Url: [FRAME_URL],
  Page: [PAGE_NUMBER]
}
</code></pre></td><td></td></tr><tr><td>ipaperEvent_productAddedToCart</td><td><pre class="language-javascript"><code class="lang-javascript">{
  Id: [PRODUCT_ID],
  Name: [PRODUCT_NAME],
  Page: [PAGE_NUMBER]
}
</code></pre></td><td></td></tr><tr><td>ipaperEvent_newsTickerExternalLinkClick</td><td><pre class="language-javascript"><code class="lang-javascript">{
  Url: [EXTERNAL_URL]
}
</code></pre></td><td></td></tr><tr><td>ipaperEvent_search</td><td><pre class="language-javascript"><code class="lang-javascript">{
  Term: [SEARCH_TERM]
}
</code></pre></td><td></td></tr><tr><td>ipaperEvent_shopCheckout</td><td><pre class="language-javascript"><code class="lang-javascript">{
  NumberOfProducts: [NO_OF_PRODUCT_IN_CART],
  CheckoutType: [CHECKOUT_TYPE],
  BasketValue: [TOTAL_BASKET_VALUE]
}
</code></pre></td><td>Possible <code>CheckoutType</code> values are <code>Email</code>, <code>ShareEmail</code>, <code>Checkout</code>, or <code>Print</code>.</td></tr><tr><td>ipaperEvent_videoPlay</td><td><pre class="language-javascript"><code class="lang-javascript">{
  Url: [VIDEO_URL],
  VideoType: [VIDEO_TYPE],
  Page: [PAGE_NUMBER]
}
</code></pre></td><td><strong>Only the first play is registered.</strong> Possible <code>VideoType</code> values are <code>Video</code>, <code>YouTube</code>, or <code>Vimeo</code>.</td></tr><tr><td>ipaperEvent_amountSelectorModalOpen</td><td><pre class="language-javascript"><code class="lang-javascript">{
  Id: [PRODUCT_ID],
  Page: [PAGE_NUMBER]
}
</code></pre></td><td></td></tr><tr><td>ipaperEvent_productsOnPageModalOpen</td><td><pre class="language-javascript"><code class="lang-javascript">{
  Page: [PAGE_NUMBER]
}
</code></pre></td><td></td></tr><tr><td>ipaperEvent_shopDetailItemModalOpen</td><td><pre class="language-javascript"><code class="lang-javascript">{
  Id: [PRODUCT_ID],
  Page: [PAGE_NUMBER]
}
</code></pre></td><td></td></tr><tr><td>ipaperEvent_shopMultivariantModalOpen</td><td><pre class="language-javascript"><code class="lang-javascript">{
  Page: [PAGE_NUMBER]
}
</code></pre></td><td></td></tr></tbody></table>

#### **Example**

An example of a DataLayer push for the event `ipaperEvent_externalLinkClick` could look like this:

```javascript
window.dataLayer.push({
	'event': 'ipaperEvent_externalLinkClick',
	'ipaperEventParams': {
		HoverText: 'The clicked links hover text', 
		Url: 'http://ipaper.io/', 
		Page: 12
	},
	'ipaperEventCategory': 'iPaper events',
	'ipaperEventAction': 'External Link Click',
	'ipaperEventLabel': 'HoverText: The clicked links hover text | Url: http://ipaper.io/ | Page: 12'
})
```

## Page element click tracking on GTM

Aside from the events listed above, we also track the `PageElementClicked` event, but it’s only triggered for the **internal** & **external** linktype.

<table><thead><tr><th>Event name</th><th>Data</th><th>Comment</th></tr></thead><tbody><tr><td>PageElementClicked</td><td><pre class="language-javascript"><code class="lang-javascript">{
  Type: 'internal' | 'external',
  Data: {
    Url: String,
    Pagenumber: String
  }
}
</code></pre></td><td>Triggered on <strong>internal</strong> and <strong>external</strong> linktypes <em>only</em></td></tr></tbody></table>

## Event labels in GA and GTM

The data format below is represented in JSON. It is converted to a string for the `eventLabel` object using the following convention:

* Key names are PascalCased
* Key-value pairs are separated by the pipe character `|`

Example:

```javascript
{
  HoverText: [LINK_HOVER_TEXT],
  Url: [EXTERNAL_URL],
  Page: [PAGE_NUMBER]
}
```

…is converted into:

`HoverText: [LINK_HOVER_TEXT] | Url: [EXTERNAL_URL] | Page: [PAGE_NUMBER]`


# Overview

## API reference

Our API uses resource-oriented URLs, accepts [form-encoded request](https://en.wikipedia.org/wiki/POST_\(HTTP\)#Use_for_submitting_web_forms) bodies, returns [XML](https://en.wikipedia.org/wiki/XML) responses, and authentication.

Our API provides an easy way for you to integrate with the iPaper Flipbook product from your backend.

You can use the API to access the most common actions also available from our CMS, which allows you to build your own software that leverages the iPaper product.

## General Guidelines

* If an API returns a humanly read message, do not rely on it. It may change over time without notification. The message is only intended to help you understand the return code.
* Anything but a SUCCESS return code should be treated as an error. An ERROR return code may be changed with a more specific return code at a later stage, but a SUCCESS code will never be changed without previous notification.
* Any breaking changes will either be notified in due time, or a new API version will be setup so the old one can run concurrently.
* API methods may be appended without there being created a new overall API version, but only as long as they add non-breaking features like optional fields and extra return data. As a result of this, do use XPATH to query results, you should not rely on element index positions.

## Best practice

* **Keep your credentials safe**\
  While our endpoint allows for you to use GET requests, we strongly suggest you use POST. Using GET requests will send any credentials as clear text over the internet even if sent via HTTPS.
* **Rate limit your requests**\
  You should keep requests to our API below **50 per minute**.\
  Some requests like for instance `Paper.GetAllPapers` will return the same result every time unless someone/something has made changes since the last request, so consider adding a short lived cache on your end, or only make calls when you know there there are changes.
* **Use caching where possible**\
  Depending on the endpoint you call in the API, you should cache responses when possible.\
  Caching results that aren’t prone to changes will make your software faster since you save a roundtrip to our API for data you already have.
* **Use the proper abstractions**\
  If you have a website or app that uses data from iPaper, you should create an endpoint in your backend that your services talk to. Having your own endpoint allows you to manage caching, hide credentials, transform API results and much more.\
  Don’t call our API directly from any code that runs on user devices, e.g. apps, websites, etc. as this introduces the risk of end-users grabbing the credentials and misusing them.
* **Dedicated user**\
  We highly recommend creating a user dedicated to API usage. \
  This helps safeguard your company from unfortunate situations, such as API functionality stop working due to having deleted a retired employees personal credentials.

## Accessing our flipbook backend API

When accessing our flipbook backend API there are two fields that are always present:

* **Username**\
  The username field is the email you use to access our administration system. This is a required field for all our API endpoints.
* **Password**\
  The password field is the password tied to the username of your choice, that is used to access our administration system. This is a required field for all our API endpoints.

Another way to grant access through our flipbook backend API would be using ‘APIKey’ (not case sensitive) as your username and your actual API key as your password.

| Parameter | Value             |
| --------- | ----------------- |
| Username  | APIKey            |
| Password  | \[Insert API Key] |

API Keys can be created via [account settings](https://admin.ipaper.io/account-settings). Please note that only Administrators can create/see API Keys.

### Why 2 different ways to access?

When accessing the API we allow 2 different methods of authentication. Through a users username and password or 'APIKey' and the actual API Key. We have multiple authentication methods for the following reasons:

**Two-factor authentication users**\
When accessing our API, be aware that normal user credentials are prohibited from accessing the Backend API when two-factor authentication is enabled on the user. \
\
Using API Key credentials can circumvent this and allow your license to have 2FA and use our backend API at the same time. If Forced2FA has been enabled on the account, the API Key approach is the only way to access the Backend API.\
\
There might be other restrictions going forward that will affect normal usage of our API, whereas the 'APIKey' method allows us to grant access without having to verify any other form of verification other than the API Key itself.

## API Endoint URL

The actual endpoint URL depends on which partner the solution is hosted at. For direct customers of iPaper, the endpoint is:

```
https://ipaper.api.ipapercms.dk
```

For other partners, the endpoint is:

```
https://<partner>.api.ipapercms.dk
```


# Return Codes

## CLIENT\_ALREADY\_HAD\_MODULE

## CLIENT\_INVALID\_UNIT\_COUNT

## CLIENT\_UNKNOWN

## CUSTOMFIELD\_FIELD\_DOES\_NOT\_EXIST

## CUSTOMFIELD\_FIELD\_NAME\_IS\_NOT\_UNIQUE

This code is returned if a custom field is added that does not have a unique name.

Solutions:Change the nameDelete the existing field that uses the same name.

## CUSTOMFIELD\_INVALID\_DATA\_FORMAT

This code is returned when a generic error occurs. Usually this signals an unexpected / unhandled error occured. Some API methods are more granular in their error responses than others, and thus a method might also return an ERROR for specific and/or more common errors. An API method may begin returning more specific error codes at a later stage, but any non-success code will always indiciate an error condition.

## CUSTOMFIELD\_INVALID\_DATATYPE

## CUSTOMFIELD\_INVALID\_FIELD\_NAME

## ERROR

## LICENSE\_DOES\_NOT\_ALLOW\_PROCESSING

This error is returned if the license of the iPaper does not allow further processing. The license will have to be upgraded to allow further processings.

## LOGIN\_2FA\_ENABLED

## LOGIN\_INVALID\_CREDENTIALS

## LOGIN\_NO\_API\_ACCESS

## LOGIN\_RATE\_LIMITED

## LOGIN\_SECURITY\_VIOLATION

## LOGIN\_SSO\_ENABLED

## PAPER\_DOES\_NOT\_EXIST

Returned whenever an iPaper ID is requested that does not exist.

## PAPER\_MUST\_BE\_CATEGORY

## PAPER\_MUST\_NOT\_BE\_CATEGORY

## PAPER\_SETTING\_NOT\_ALLOWED

## READONLY

During certain upgrade procedures, we will have to put the system in a read only mode. The frontend will be fully functional, as will all API read operations. Logins to the backend will be blocked, and API functions modifying data will return the READONLY status.

## RIPPER\_CANNOT\_PROCESS\_PAPER\_ALREADY\_BEING\_PROCESSED

## RIPPER\_CANNOT\_REPROCESS\_AN\_UNPROCESSED\_PAPER

## RIPPER\_CANNOT\_REPROCESS\_PAPER\_WITH\_NO\_PROCESSING\_DETAILS

A given iPaper can only be processed with one PDF at a time. This error is returned if an attempt is made to start a new process of an iPaper that is already being processed.

## RIPPER\_FILE\_IS\_NOT\_PDF

## RIPPER\_INVALID\_BASE64\_PDF\_FILE

## RIPPER\_PDF\_FILE\_DOES\_NOT\_EXIST

This code is returned if a PDF file was not provided. The provided base64 string could not be parsed / was invalid.

## SEARCH\_CUSTOMFIELD\_DATATYPE\_DOES\_NOT\_SUPPORT\_WILDCARDS

## SEARCH\_NO\_CUSTOM\_FIELD\_CRITERIAS

## SEARCH\_TEMPORARILY\_UNAVAILABLE

## STATISTICS\_TEMPORARILY\_UNAVAILABLE

## SUCCESS

The SUCCESS return code indicates that the requested operation completed successfully. Further information / requested data may be available in the messsage and data fields. Any method that performs a request without errors & problems will always return a SUCCESS code.

## UNSUPPORTED\_CHECKOUT\_TYPE


# Ecommerce

{% content-ref url="/pages/PLpOipCQPKS8o4Uh1JHU" %}
[GetFlipbookOrdersByType](/flipbook-backend-api/ecommerce/get-orders-for-flipbook)
{% endcontent-ref %}

{% content-ref url="/pages/ajKi3DHFHPHbWBFCVStB" %}
[GetLicenseOrdersByType](/flipbook-backend-api/ecommerce/get-orders-for-license)
{% endcontent-ref %}


# GetFlipbookOrdersByType

## Description

Returns an XML document listing all orders completed over the last 3 months. Dates returned in the response are UTC time zone.

## Parameters

| Name         | Values                                                                                                                                                                                                                                                                    |
| ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| paperId      | The paper id of the flipbook to return orders for                                                                                                                                                                                                                         |
| checkoutType | <p>The name of the checkout action that resulted in the orders.<br>Parameter can be omitted to return orders of any origin.<br>Supported checkout types:</p><ul><li>CheckoutByEmail</li><li>CheckoutByExport</li><li>CheckoutByPrint</li><li>CheckoutByWhatsApp</li></ul> |

## Sample Response

```xml
<result>
	<code>
		<![CDATA[ SUCCESS ]]>
	</code>
	<message>
		<![CDATA[ Orders returned. ]]>
	</message>
	<data>
		<flipbooks>
			<flipbook id="1337" name="Sample Flipbook" url="/ipaper/sample-flipbook/">
				<orders count="1" earliestOrderDate="2020-06-25 11:57:35" latestOrderDate="2020-06-25 11:57:35">
					<order orderId="a1B2c3" orderDate="2020-06-25 11:57:35" checkoutType="CheckoutByEmail" orderLinesCount="2" totalOrderValue="5351,8">
						<orderLine>
							<productId>
								<![CDATA[ A123 ]]>
							</productId>
							<productName>
								<![CDATA[ Sample product 1 ]]>
							</productName>
							<productDescription>
								<![CDATA[ A description for Sample product 1 ]]>
							</productDescription>
							<productPrice>
								<![CDATA[ 1337,95 ]]>
							</productPrice>
							<amount>
								<![CDATA[ 2 ]]>
							</amount>
						</orderLine>
						<orderLine>
							<productId>
								<![CDATA[ A456 ]]>
							</productId>
							<productName>
								<![CDATA[ Sample product 2 ]]>
							</productName>
							<productDescription>
								<![CDATA[ A description for Sample product 2 ]]>
							</productDescription>
							<productPrice>
								<![CDATA[ 1337,95 ]]>
							</productPrice>
							<amount>
								<![CDATA[ 2 ]]>
							</amount>
						</orderLine>
					</order>
				</orders>
			</flipbook>
		</flipbooks>
	</data>
</result>
```

## Sample Error Response

If an invalid checkout type is sent the following error code will be returned.

```xml
<?xml version="1.0" encoding="utf-8"?>
<result>
  <code><![CDATA[UNSUPPORTED_CHECKOUT_TYPE]]></ code>
  <message><![CDATA[Checkout type is not supported]]></message>
  <data>
    <![CDATA[ Value of 'checkoutType' is not supported. Supported values: "", "CheckoutByEmail", "CheckoutByExport", "CheckoutByPrint", "CheckoutByWhatsApp" ]]>
  </data>
</result>
```

## Return Codes

* SUCCESS
* ERROR
* READONLY
* LOGIN\_SECURITY\_VIOLATION
* UNSUPPORTED\_CHECKOUT\_TYPE


# GetLicenseOrdersByType

## Description

Returns an XML document listing all orders completed over the last 3 months. Dates returned in the response are UTC time zone.

## Parameters

| Name         | Values                                                                                                                                                                                                                                                                    |
| ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| checkoutType | <p>The name of the checkout action that resulted in the orders.<br>Parameter can be omitted to return orders of any origin.<br>Supported checkout types:</p><ul><li>CheckoutByEmail</li><li>CheckoutByExport</li><li>CheckoutByPrint</li><li>CheckoutByWhatsApp</li></ul> |

Orders will be returned for the license the authenticated user belongs to.

## Sample Response

```xml
<result>
	<code>
		<![CDATA[ SUCCESS ]]>
	</code>
	<message>
		<![CDATA[ Orders returned. ]]>
	</message>
	<data>
		<flipbooks>
			<flipbook id="1337" name="Sample Flipbook" url="/ipaper/sample-flipbook/">
				<orders count="1" earliestOrderDate="2020-06-25 11:57:35" latestOrderDate="2020-06-25 11:57:35">
					<order orderId="a1B2c3" orderDate="2020-06-25 11:57:35" checkoutType="CheckoutByEmail" orderLinesCount="2" totalOrderValue="5351,8">
						<orderLine>
							<productId>
								<![CDATA[ A123 ]]>
							</productId>
							<productName>
								<![CDATA[ Sample product 1 ]]>
							</productName>
							<productDescription>
								<![CDATA[ A description for Sample product 1 ]]>
							</productDescription>
							<productPrice>
								<![CDATA[ 1337,95 ]]>
							</productPrice>
							<amount>
								<![CDATA[ 2 ]]>
							</amount>
						</orderLine>
						<orderLine>
							<productId>
								<![CDATA[ A456 ]]>
							</productId>
							<productName>
								<![CDATA[ Sample product 2 ]]>
							</productName>
							<productDescription>
								<![CDATA[ A description for Sample product 2 ]]>
							</productDescription>
							<productPrice>
								<![CDATA[ 1337,95 ]]>
							</productPrice>
							<amount>
								<![CDATA[ 2 ]]>
							</amount>
						</orderLine>
					</order>
				</orders>
			</flipbook>
		</flipbooks>
	</data>
</result>
```

## Sample Error Response

If an invalid checkout type is sent the following error code will be returned.

```xml
<?xml version="1.0" encoding="utf-8"?>
<result>
  <code><![CDATA[UNSUPPORTED_CHECKOUT_TYPE]]></ code>
  <message><![CDATA[Checkout type is not supported]]></message>
  <data>
    <![CDATA[ Value of 'checkoutType' is not supported. Supported values: "", "CheckoutByEmail", "CheckoutByExport", "CheckoutByPrint", "CheckoutByWhatsApp" ]]>
  </data>
</result>
```

## Return Codes

* SUCCESS
* ERROR
* READONLY
* LOGIN\_SECURITY\_VIOLATION
* UNSUPPORTED\_CHECKOUT\_TYPE


# Image

{% content-ref url="/pages/9dkhEBs0c5IkwXsCJe2W" %}
[GetImageWith2PxDropShadow](/flipbook-backend-api/image/get-image-with-2px-drop-shadow)
{% endcontent-ref %}


# GetImageWith2PxDropShadow

## Description

This method will output the requested image with a 2px drop shadow around it. If successful, the image will be returned directly. If unsucessful, an error will be returned in XML format.

## Parameters

| Name      | Value                                                                                       |
| --------- | ------------------------------------------------------------------------------------------- |
| pageID    | The PageID of the page whose images should be retrieved                                     |
| imageType | <p>The type of image to be retrieved</p><ul><li>Thumb</li><li>Normal</li><li>Zoom</li></ul> |

## Sample Response

*The binary image data will be returned in raw*.

## Sample Error Response

If an error occurs in one of the field operations, the field name will be included in the data section.

## Sample Error Response

```xml
<?xml version="1.0" encoding="utf-8"?>
<result>
  <code><![CDATA[ERROR]]></code>
  <message><![CDATA[Unexpected error.]]></message>
</result>
```

## Return Codes

* ERROR


# Link

{% content-ref url="/pages/TUfvcgDjS51vXjxZA3WE" %}
[GetLinks](/flipbook-backend-api/link/get-links)
{% endcontent-ref %}


# GetLinks

## Description

Exports all links from a given iPaper.

{% hint style="info" %}
**Note:**

Superfluous nodes will be removed later on. Thus, for example, a rectangle link will in the future not include the polygoncoords node. When reading the response, you should handle potential missing nodes and degrade gracefully.
{% endhint %}

## Parameters

| Name    | Value                                               |
| ------- | --------------------------------------------------- |
| paperID | The ID of the iPaper whose links should be returned |

## Sample Response

```xml
<?xml version="1.0" encoding="utf-8"?>
<result>
  <code><![CDATA[SUCCESS]]></ code>
  <message><![CDATA[Links exported.]]></message>
  <data>
    <root>
      <link number="1">
        <number><![CDATA[2]]></number>
        <x><![CDATA[0.277648839556004]]></x>
        <y><![CDATA[0.231397006414825]]></y>
        <width><![CDATA[0.0671285252555119]]></width>
        <height><![CDATA[0.0132451983038164]]></height>
        <alttext><![CDATA[]]></alttext>
        <url><![CDATA[]]></url>
        <stylename><![CDATA[1']]></stylename>
        <shapetype><![CDATA[rectangle]]></shapetype>
        <polygoncoords><![CDATA[]]></polygoncoords>
        <linktype><![CDATA[shopitem]]></linktype>
        <fileid><![CDATA[]]></fileid>
        <target><![CDATA[]]></target>
        <usemask><![CDATA[True]]></usemask>
        <param><![CDATA[<item><name><![CDATA[FIBERROND. 3M 581C Ø127x22MM P60]]></name><price><![CDATA[2.75]]></price><descr><![CDATA[]]></descr><id><![CDATA[9900002430]]></id></item>]]></param>
      </link>
      <link number="2">
        <number><![CDATA[2]]></number>
        <x><![CDATA[0.114934409687185]]></x>
        <y><![CDATA[0.331147540983607]]></y>
        <width><![CDATA[0.0671285252555116]]></width>
        <height><![CDATA[0.0132451983038164]]></height>
        <alttext><![CDATA[]]></alttext>
        <url><![CDATA[]]></url>
        <stylename><![CDATA[1']]></stylename>
        <shapetype><![CDATA[rectangle]]></shapetype>
        <polygoncoords><![CDATA[]]></polygoncoords>
        <linktype><![CDATA[shopitem]]></linktype>
        <fileid><![CDATA[]]></fileid>
        <target><![CDATA[]]></target>
        <usemask><![CDATA[True]]></usemask>
        <param><![CDATA[<item><name><![CDATA[FIBERROND. 3M 581C Ø127X22MM P50]]></name><price><![CDATA[2.75]]></price><descr><![CDATA[]]></descr><id><![CDATA[9900002429]]></id></item>]]></param>
      </link>
</result>
```

## Return Codes

* SUCCESS
* ERROR


# Media

{% content-ref url="/pages/O7EZloU2vsXm5F3gDChr" %}
[CreateDirectory](/flipbook-backend-api/media/create-directory)
{% endcontent-ref %}

{% content-ref url="/pages/p5uP6GrquFgsjQABW4bF" %}
[DeleteDirectory](/flipbook-backend-api/media/delete-directory)
{% endcontent-ref %}

{% content-ref url="/pages/R4jtyqEjRaodS8HW64ur" %}
[DeleteFile](/flipbook-backend-api/media/delete-file)
{% endcontent-ref %}

{% content-ref url="/pages/7rhSjvGGAOxtYnCxSyvD" %}
[GetTree](/flipbook-backend-api/media/get-tree)
{% endcontent-ref %}

{% content-ref url="/pages/lubr3ykq0l0Hia2F10eY" %}
[UploadFile](/flipbook-backend-api/media/upload-file)
{% endcontent-ref %}


# CreateDirectory

## Description

Creates a directory at the specified location.

## Parameters

| Name     | Value                                                                   |
| -------- | ----------------------------------------------------------------------- |
| parentID | The ID of the parent directory. Use 0 to create a directory in the root |
| name     | The name of the field whose value should be retrieved                   |

## Sample Response

```xml
<?xml version="1.0" encoding="utf-8"?>
<result>
  <code>
    <![CDATA[ SUCCESS ]]>
  </code>
  <message></message>
  <data>
    <directoryid>948</directoryid>
  </data>
</result>
```

## Return Codes

* SUCCESS
* ERROR
* LOGIN\_SECURITY\_VIOLATION


# DeleteDirectory

## Description

Deletes a given directory.

## Parameters

| Name        | Value                             |
| ----------- | --------------------------------- |
| directoryID | The ID of the directory to delete |

## Sample Response

```xml
<?xml version="1.0" encoding="utf-8"?>
<result>
  <code>
    <![CDATA[ SUCCESS ]]>
  </code>
  <message></message>
</result>
```

## Return Codes

* SUCCESS
* ERROR
* LOGIN\_SECURITY\_VIOLATION


# DeleteFile

## Description

Deletes a given directory.

## Parameters

| Name   | Value                        |
| ------ | ---------------------------- |
| fileID | The ID of the file to delete |

## Sample Response

```xml
<?xml version="1.0" encoding="utf-8"?>
<result>
  <code>
    <![CDATA[ SUCCESS ]]>
  </code>
  <message></message>
</result>
```

## Return Codes

* SUCCESS
* ERROR
* LOGIN\_SECURITY\_VIOLATION


# GetTree

## Description

Returns an XML document listing all directories and files the user has access to. Sizes are in KB.

## Sample Response

```xml
<?xml version="1.0" encoding="utf-8"?>
<result>
  <code><![CDATA[SUCCESS]]></ code>
  <message></message>
  <data>
    <directory directoryid="809" name="PDF">
      <file fileid="127" name="A.pdf" size="17423"/>
      <file fileid="1348" name="B.pdf" size="681323"/>
    </directory>
    <directory directoryid="843" name="Logos"/>
      <directory directoryid="844" name="Client A">
        <file fileid="1348" name="LogoA.jpg" size="109"/>
      </directory>
      <directory directoryid=872" name="Client B">
        <file fileid="2512" name="LogoB.jpg" size="1039"/>
      </directory>
    </directory>
  </data>
</result>
```

## Return Codes

* SUCCESS
* ERROR
* LOGIN\_SECURITY\_VIOLATION


# UploadFile

## Description

Uploads a file to the specified directory. If the file already exists (matching on the name), it will be overwritten. Otherwise a new file will be created. In either case, the file ID, whether created or updated, will be returned.

## Parameters

| Name       | Value                                                                                       |
| ---------- | ------------------------------------------------------------------------------------------- |
| parentID   | The ID of the directory into which the file should be uploaded. Use 0 to upload to the root |
| name       | The name of the uploaded file                                                               |
| base64File | The binary file contents encoded as a base64 string                                         |

## Sample Response

```xml
<?xml version="1.0" encoding="utf-8"?>
<result>
  <code>
    <![CDATA[ SUCCESS ]]>
  </code>
  <message></message>
  <data>
    <fileid>1583</fileid>
  </data>
</result>
```

## Return Codes

* SUCCESS
* ERROR
* LOGIN\_SECURITY\_VIOLATION


# Paper

{% content-ref url="/pages/ORCIheaLJK45xgVzbAEH" %}
[ChangePaperUrl](/flipbook-backend-api/paper/change-paper-url)
{% endcontent-ref %}

{% content-ref url="/pages/B8Pg867h2IkBcm9w4wQt" %}
[ClearPublicationPeriod](/flipbook-backend-api/paper/clear-publication-period)
{% endcontent-ref %}

{% content-ref url="/pages/GgQtv6UqgQmdOMEo1cxG" %}
[CreateCategory](/flipbook-backend-api/paper/create-category)
{% endcontent-ref %}

{% content-ref url="/pages/jRDOjYXHbPra9kzWsT5F" %}
[CreatePaper](/flipbook-backend-api/paper/create-paper)
{% endcontent-ref %}

{% content-ref url="/pages/9XCvJkq7k2wWr08vUK54" %}
[CreatePreviewToken](/flipbook-backend-api/paper/create-preview-token)
{% endcontent-ref %}

{% content-ref url="/pages/vZwfUnhNADy1bdB4rXVx" %}
[DeletePaper](/flipbook-backend-api/paper/delete-paper)
{% endcontent-ref %}

{% content-ref url="/pages/TTqA94x92TeYFaGlxwrq" %}
[GetAllPapers](/flipbook-backend-api/paper/get-all-papers)
{% endcontent-ref %}

{% content-ref url="/pages/8Sf5Sd4FEAgVmKxtZBQe" %}
[GetPaperIDFromUrl](/flipbook-backend-api/paper/get-paper-id-from-url)
{% endcontent-ref %}

{% content-ref url="/pages/uACsogQpt0Xjsm7e5Gta" %}
[GetPaperStructure](/flipbook-backend-api/paper/get-paper-structure)
{% endcontent-ref %}

{% content-ref url="/pages/xflvMrEyb9OlsOC4mQvs" %}
[GetPaperUrlFromID](/flipbook-backend-api/paper/get-paper-url-from-id)
{% endcontent-ref %}

{% content-ref url="/pages/RDWEOSxYDlIKusN0Zf86" %}
[GetSetting](/flipbook-backend-api/paper/get-setting)
{% endcontent-ref %}

{% content-ref url="/pages/hBMvIsQaT3HQMPNwnJFX" %}
[GetSettingDescriptions](/flipbook-backend-api/paper/get-setting-descriptions)
{% endcontent-ref %}

{% content-ref url="/pages/NAstjCH2g3rzLa8fHN4m" %}
[MovePaper](/flipbook-backend-api/paper/move-paper)
{% endcontent-ref %}

{% content-ref url="/pages/3qGnns8hHbS1MnAq3NKX" %}
[SetPublicationPeriod](/flipbook-backend-api/paper/set-publication-period)
{% endcontent-ref %}

{% content-ref url="/pages/DCOtcEh8cFrWf2aaXoMM" %}
[SetSetting](/flipbook-backend-api/paper/set-setting)
{% endcontent-ref %}


# ChangePaperUrl

## Description

Changes a papers URL.

## Parameters

| Name    | Value                                              |
| ------- | -------------------------------------------------- |
| paperID | The ID of the iPaper whose URL should be changed   |
| newUrl  | The new relative URL of the iPaper. Must be unique |

## Sample Response

```xml
<?xml version="1.0" encoding="utf-8"?>
<result>
  <code><![CDATA[SUCCESS]]></code>
  <message><![CDATA[Url changed.]]></message>
</result>
```

## Return Codes

* SUCCESS
* ERROR
* READONLY


# ClearPublicationPeriod

## Description

Clears the publication period of a Flipbook, it will always be visible without a publication period.

## Parameters

| Name    | Value                                                             |
| ------- | ----------------------------------------------------------------- |
| paperID | The ID of the Flipbook whose publication period should be changed |

## Sample Response

```xml
<?xml version="1.0" encoding="utf-8"?>
<result>
  <code><![CDATA[SUCCESS]]></code>
  <message><![CDATA[Publication period cleared.]]></message>
</result>
```

## Return Codes

* SUCCESS
* ERROR
* READONLY


# CreateCategory

## Description

Creates a category under the specified parent category id.

## Parameters

| Name    | Value                                                           |
| ------- | --------------------------------------------------------------- |
| paperID | The ID of the iPaper whose publication period should be changed |
| name    | The name of the new category                                    |
| urlName | The name of the category as used in URLs                        |

## Sample Response

```xml
<?xml version="1.0" encoding="utf-8"?>
<result>
  <code><![CDATA[SUCCESS]]></code>
  <message><![CDATA[Category created.]]></message>
  <data>
    <paper id="1240" />
  </data>
</result>
```

## Return Codes

* SUCCESS
* ERROR
* READONLY


# CreatePaper

## Description

Creates a paper under the specified parent category id.

## Parameters

| Name     | Value                                                               |
| -------- | ------------------------------------------------------------------- |
| parentID | The ID of the category under which the new iPaper should be created |
| name     | The name of the new iPaper                                          |
| urlName  | The name of the iPaper as used in URLs                              |

## Sample Response

```xml
<?xml version="1.0" encoding="utf-8"?>
<result>
  <code><![CDATA[SUCCESS]]></code>
  <message><![CDATA[Paper created.]]></message>
  <data>
    <paper id="1242" />
  </data>
</result>
```

## Return Codes

* SUCCESS
* ERROR
* READONLY


# CreatePreviewToken

## Description

Creates a preview SecurityToken that can be used to bypass any security on the Flipbook. After creating a token for a specific Flipbook, a specific client IP address and for a given time period, the Flipbook may be accessed using the URL: `<urlToFlipbook>/?SecurityToken=<token>` to bypass any security. This is typically used for scenarios where the user is logged in to an intranet and does not want to login to see the Flipbooks, while Flipbooks should be protected from public viewing. Instead of linking directly to the Flipbooks from the intranet, the intranet should link to a local page that creates the securitytoken and then redirects the user to the catalog.

Typical workflow:

1. \[Intranet] User clicks link to Flipbook.
2. \[Intranet] Requests security token from iPaper.
3. \[iPaper] Security token created & returned.
4. \[Intranet] Redirects user to actual Flipbook with securitytoken appended

## Parameters

| Name            | Value                                                                                                                                                               |
| --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| paperID         | The ID of the iPaper that should be granted access to                                                                                                               |
| validityMinutes | How long the security token should be valid for                                                                                                                     |
| ip              | The IP that should be granted access. If this is en empty string, no IP validation will be performed and any IP presenting the correct token will be granted access |

## Sample Response

```xml
<?xml version="1.0" encoding="utf-8"?>
<result>
  <code><![CDATA[SUCCESS]]></code>
  <message><![CDATA[Preview token created.]]></message>
  <data>
    <token><![CDATA[ffc8d256-c727-4bc7-9a32-67a929f4fa53]]></token>
  </data>
</result>
```

## Return Codes

* SUCCESS
* ERROR
* LOGIN\_SECURITY\_VIOLATION


# DeletePaper

## Description

Deletes the specified paper.

## Parameters

| Name    | Value                                       |
| ------- | ------------------------------------------- |
| paperID | The ID of the iPaper that should be deleted |

## Sample Response

```xml
<?xml version="1.0" encoding="utf-8"?>
<result>
  <code><![CDATA[SUCCESS]]></code>
  <message><![CDATA[Paper deleted.]]></message>
</result>
```

## Return Codes

* SUCCESS
* ERROR
* READONLY


# GetAllPapers

## Description

Returns an XML document listing all categories and iPapers below, and including, the requested PaperID. If a publication period restriction is set on the iPaper, the publicationperiod-from and publicationperiod-to fields will be present. Publication periods returned in the response are UTC time zone.

## Parameters

| Name    | Value                                                                              |
| ------- | ---------------------------------------------------------------------------------- |
| paperID | The ID of the iPaper that should be the root level of the tree of iPapers returned |

## Sample Response

```xml
<?xml version="1.0" encoding="utf-8"?>
<result>
  <code><![CDATA[SUCCESS]]></code>
  <message><![CDATA[List of papers retrieved.]]></message>
  <data>
    <category id="182" name="Partner" url="/">
      <category id="173" name="Demos" url="/demos/">
        <category id="1240" name="hep23" url="/demos/hep23/" />
        <paper id="1106" name="21" url="/demos/21/" />
        <paper id="1114" name="CC" url="/demos/CC/" />
        <paper id="1120" name="Dot_Matrix_Range" url="/demos/Dot_Matrix_range/" publicationperiod-from="2014-05-14 01:25:00" publicationperiod-to="2014-05-29 01:35:00" />
        <paper id="1117" name="edc" url="/demos/edc/" />
        <paper id="1233" name="ipaper" url="/demos/ipaper/" />
        <paper id="1232" name="Test" url="/demos/Test/" />
      </category>
    </category>
  </data>
</result>
```

## Return Codes

* SUCCESS
* ERROR


# GetPaperIDFromUrl

## Description

Returns the ID of an iPaper, given its url. Can also be used to check whether a url is vacant.

## Parameters

| Name | Value                                                                                           |
| ---- | ----------------------------------------------------------------------------------------------- |
| url  | The URL of the iPaper whose ID should be returned. Url must start with a `/` and end with a `/` |

## Sample Response When iPaper Not Found

```xml
<?xml version="1.0" encoding="utf-8"?>
<result>
  <code><![CDATA[SUCCESS]]></code>
  <message><![CDATA[Paper not found.]]></message>
</result>
```

## Sample Response When iPaper Is Found

```xml
<?xml version="1.0" encoding="utf-8"?>
<result>
  <code><![CDATA[SUCCESS]]></code>
  <message><![CDATA[Paper found.]]></message>
  <data>
    <paper id="1106" />
  </data>
</result>
```

## Return Codes

* SUCCESS
* ERROR


# GetPaperStructure

## Description

Returns an XML document listing all pages and page images for the requested PaperID.

{% hint style="info" %}
**Note:**

You should not reference these urls from your own systems as they may change at any point. If you want to use the images, you should download them locally and host them yourself. We also may redirect the user to other domains, so they mustn’t be references from Flash files directly, as crossdomain needs may change dynamically.
{% endhint %}

## Parameters

| Name               | Value                                                       |
| ------------------ | ----------------------------------------------------------- |
| paperID            | The ID of the iPaper whose structure should be returned     |
| includeIndexedText | If true, returns the text iPaper has indexed from each page |

## Sample Response (Without Text)

```xml
<?xml version="1.0" encoding="utf-8"?>
<result>
  <code><![CDATA[SUCCESS]]></code>
  <message><![CDATA[Paper structure retrieved.]]></message>
  <data>
    <paper id="1120" name="Dot_Matrix_Range" url="/demos/Dot_Matrix_range/">
      <pages>
        <page id="58367" number="1" part-of-index="False">
          <images>
            <image file-size="300" type="Zoom" url="http://flex.frontend.dk/demos/Dot_Matrix_range/Image.ashx?PageID=58367&amp;ImageType=Zoom" />
            <image file-size="90" type="Normal" url="http://flex.frontend.dk/demos/Dot_Matrix_range/Image.ashx?PageID=58367&amp;ImageType=Normal" />
            <image file-size="6" type="Thumb" url="http://flex.frontend.dk/demos/Dot_Matrix_range/Image.ashx?PageID=58367&amp;ImageType=Thumb" />
          </images>
        </page>
        ...
      </pages>
    </paper>
  </data>
</result>
```

## Sample Response (With Text)

```xml
<?xml version="1.0" encoding="utf-8"?>
<result>
  <code><![CDATA[SUCCESS]]></code>
  <message><![CDATA[Paper structure retrieved.]]></message>
  <data>
    <paper id="1120" name="Dot_Matrix_Range" url="/demos/Dot_Matrix_range/">
      <pages>
        <page id="58367" number="1" part-of-index="False">
          <text><![CDATA[Before Use 1. Safety precautions. 8 2. Before use . 13 Discs .15 Operation 1. About This Receiver . 18 Control Names and Operations.18 Panel Switches. 18 Turning the POWER ON or OFF .19 Switching Between the Audio and Navigation Screen .19 Turning the PND Display ON and OFF .20 Detaching the PND .20 Using the PND. 20 How to Detach the PND from the Main Unit. 20 Attaching the PND. 20 Using the Audio Cover.21 Using the Audio Cover . 21 Fitting the Audio Cover. 21 How to Detach the Audio Cover from the Main Unit. 21 Hands-Free Microphone and Light Sensor .21 Adjusting the Display Angle.22 Loading/Unloading a Disc .22 Loading Steps . 23 CD/DVD unloading steps . 24 Operation Conditions of this Receiver .25 2. Setup of This Receiver . 26 Setup of This Receiver.26 Calling the Setting screen .26 Enable guide tone . 26 Enable AUX. 26 Enable VTR . 27 Dim sub-display. 27 Link night view to headlights . 27 1]]></text>
          <images>
            <image file-size="300" type="1" url="http://flex.frontend.dk/demos/Dot_Matrix_range/Image.ashx?PageID=58367&amp;ImageType=Zoom" />
            <image file-size="90" type="2" url="http://flex.frontend.dk/demos/Dot_Matrix_range/Image.ashx?PageID=58367&amp;ImageType=Normal" />
            <image file-size="6" type="3" url="http://flex.frontend.dk/demos/Dot_Matrix_range/Image.ashx?PageID=58367&amp;ImageType=Thumb" />
          </images>
        </page>
        ...
      </pages>
    </paper>
  </data>
</result>
```

## Return Codes

* SUCCESS
* ERROR


# GetPaperUrlFromID

## Description

Returns the url of an iPaper, given its ID.

## Parameters

| Name    | Value                                             |
| ------- | ------------------------------------------------- |
| paperID | The ID of the iPaper whose URL should be returned |

## Sample Response (iPaper Found)

```xml
<?xml version="1.0" encoding="utf-8"?>
<result>
  <code><![CDATA[SUCCESS]]></code>
  <message><![CDATA[Paper found.]]></message>
  <data>
    <paper url="/demos/21/" />
  </data>
</result>
```

## Sample Response (iPaper Not Found)

```xml
<?xml version="1.0" encoding="utf-8"?>
<result>
  <code><![CDATA[PAPER_DOES_NOT_EXIST]]></code>
  <message><![CDATA[Paper not found.]]></message>
</result>
```

## Return Codes

* SUCCESS
* ERROR
* PAPER\_DOES\_NOT\_EXIST


# GetSetting

## Description

Returns the specified setting value for the specified PaperID.

## Parameters

| Name    | Value                                                                                                                                                              |
| ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| paperID | The ID of the iPaper whose setting value should be returned                                                                                                        |
| setting | The name of the setting whose value should be returned. See [GetSettingDescriptions](/flipbook-backend-api/paper/get-setting-descriptions) for valid setting names |

## Sample Response

```xml
<?xml version="1.0" encoding="utf-8"?>
<result>
  <code><![CDATA[SUCCESS]]></code>
  <message><![CDATA[Setting retrieved.]]></message>
  <data>
    <iconPdf><![CDATA[true]]></iconPdf>
  </data>
</result>
```

## Return Codes

* SUCCESS
* ERROR


# GetSettingDescriptions

## Description

Returns a list of all the settings that can be accessed using the Get/SetSetting methods.

## Sample Response

```xml
<?xml version="1.0" encoding="utf-8"?>
<result>
  <code><![CDATA[SUCCESS]]></code>
  <message><![CDATA[Setting descriptions retrieved.]]></message>
  <data>
    <settings>
      <setting name="autoMaximize" description="Auto maximize" explanation="Whether the iPaper window should automatically maximize when opened." type="bool" />
      ...
    </settings>
  </data>
</result>
```

## Return Codes

* SUCCESS
* ERROR


# MovePaper

## Description

Moves an iPaper (`sourcePaperID`) from it’s current parent to a new parent (`destinationParentID`).

## Parameters

| Name                | Value                                                                   |
| ------------------- | ----------------------------------------------------------------------- |
| sourcePaperID       | The ID of the iPaper that should be deleted                             |
| destinationParentID | The ID of the category under which the iPaper/Category should be placed |

## Sample Response

```xml
<?xml version="1.0" encoding="utf-8"?>
<result>
  <code><![CDATA[SUCCESS]]></code>
  <message><![CDATA[Paper moved.]]></message>
</result>
```

## Return Codes

* SUCCESS
* ERROR
* READONLY


# SetPublicationPeriod

## Description

Sets the publication period of an iPaper, it will only be visible within the publication period. **From** and **to** parameters only accepts UTC dates.

## Parameters

| Name    | Value                                                           |
| ------- | --------------------------------------------------------------- |
| paperID | The ID of the iPaper whose publication period should be changed |
| from    | The beginning of the publication period                         |
| to      | The end of the publication period                               |

## Sample Response

```xml
<?xml version="1.0" encoding="utf-8"?>
<result>
  <code><![CDATA[SUCCESS]]></code>
  <message><![CDATA[Publication period cleared.]]></message>
</result>
```

## Return Codes

* SUCCESS
* ERROR
* READONLY


# SetSetting

## Description

Sets the specified setting value for the specified PaperID.

## Parameters

| Name    | Value                                                                                                                                                              |
| ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| paperID | The ID of the iPaper whose setting value should be returned                                                                                                        |
| setting | The name of the setting whose value should be returned. See [GetSettingDescriptions](/flipbook-backend-api/paper/get-setting-descriptions) for valid setting names |
| value   | The value to set. Value will be validated according to the setting type                                                                                            |

## Sample Response

```xml
<?xml version="1.0" encoding="utf-8"?>
<result>
  <code><![CDATA[SUCCESS]]></code>
  <message><![CDATA[Setting set.]]></message>
</result>
```

## Return Codes

* SUCCESS
* ERROR
* PAPER\_SETTING\_NOT\_ALLOWED
* READONLY


# Ripper

{% content-ref url="/pages/KKw0GRN1rzDZ2syaUxxM" %}
[GetProcessingPapers](/flipbook-backend-api/ripper/get-processing-papers)
{% endcontent-ref %}

{% content-ref url="/pages/gZIzIa6ke0O4MxgOmKjC" %}
[GetProcessingStatus](/flipbook-backend-api/ripper/getprocessingstatus)
{% endcontent-ref %}

{% content-ref url="/pages/Hz3HTRwaUJrBq1ChyvRT" %}
[ProcessPDF](/flipbook-backend-api/ripper/process-pdf)
{% endcontent-ref %}


# GetProcessingPapers

## Description

Returns a list of Flipbooks currently being processed with dates the individual papers were created in UTC, PaperID, Job-GUID and the current progress in percentage.

## Sample Response

```xml
<?xml version="1.0" encoding="utf-8"?>
<result>
  <code><![CDATA[SUCCESS]]></code>
  <message><![CDATA[List retrieved successfully.]]></message>
  <data>
    <processes>
      <process created="2009-03-16 18:00:07" paper-id="1120" job-guid="05d5ef54-6667-47f7-97c3-41bec10438d7" progress="0" />
    </processes>
  </data>
</result>
```

## Return Codes

* SUCCESS
* ERROR


# GetProcessingStatus

## Description

Returns the status of a processing job. The status can be "Completed", "EnrichmentProcessingFailed" or "Failed". If the job is still pending processing, then the process status will be "Pending".

"Completed" status - The whole processing is successful. The Ripper processed the PDF, imported the links and ran the Enrichment Automation (if such was provided) successfully.

"EnrichmentProcessingFailed" status - The Ripper failed to import the links or run the Enrichment Automation, or failed to do both.

"Failed" status - The Ripper failed to process the PDF and therefore links cannot be imported and the Enrichment Automation cannot be run.

## Parameters

| Name    | Value                                            |
| ------- | ------------------------------------------------ |
| jobGuid | The Guid of the processing job to get status for |

## Sample Response

```xml
<?xml version="1.0" encoding="utf-8"?>
<result>
  <code>
    <![CDATA[ SUCCESS ]]>
  </code>
  <message>
    <![CDATA[ Status retrieved successfully. ]]>
  </message>
  <data>
    <ProcessStatus>
      <![CDATA[ Completed ]]>
    </ProcessStatus>
  </data>
</result>
```

## Return Codes

* SUCCESS
* ERROR


# ProcessPDF

## Description

Begins a processing of a Flipbook.

## Limitations

Supports pre-base64 encoded PDF sizes of up to 192MB. Selecting a PDF that exceeds this limitation will result in the failure of the request to process the PDF.

## Parameters

| Name       | Value                                                    |
| ---------- | -------------------------------------------------------- |
| paperID    | The ID of the Paper to process                           |
| xmlOptions | Extra XML options for the processing request (See below) |
| base64Pdf  | The PDF file encoded as a base64 string                  |

### xmlOptions

<table><thead><tr><th>Name</th><th>Required</th><th width="117">Datatype</th><th width="134">Default value</th><th>Description</th></tr></thead><tbody><tr><td>preserve-index</td><td>No</td><td>bool</td><td>true</td><td>Whether to preserve the current index of the Flipbook</td></tr><tr><td>pages-from</td><td>When ‘all-pages’ = false</td><td>int</td><td>1</td><td>The starting page in the catalog processing should begin from</td></tr><tr><td>pages-to</td><td>When ‘all-pages’ = false</td><td>int</td><td>[last page of pdf]</td><td>The ending page in the catalog processing should end with</td></tr><tr><td>all-pages</td><td>No</td><td>bool</td><td>true</td><td>If true, pages-from and pages-to will be ignored, all pages will be processed. If false, pages-from and pages-to must be set</td></tr><tr><td>preserve-links</td><td>No</td><td>bool</td><td>true</td><td>Whether to preserve current links on pages or clear them</td></tr><tr><td>import-bookmarks</td><td>No</td><td>bool</td><td>false</td><td>Whether to import bookmarks from the PDF as index. Assumes preserve-index=false</td></tr><tr><td>import-links</td><td>No</td><td>bool</td><td>false</td><td>Whether to import links from the PDF. If true, ’import-links-styleid’ must be specified</td></tr><tr><td>import-links-styleid</td><td>When ‘import-links’ = true</td><td>int</td><td> </td><td>The linkstyle ID of style that imported links should assume. If you’re unsure of the ID, reach out to support</td></tr><tr><td>password</td><td>No</td><td>string</td><td> </td><td>Password to unlock protected PDF. Will fail if PDF is protected and correct password is not provided</td></tr><tr><td>preprocessors</td><td>No</td><td>XML</td><td> </td><td>Preprocessors that needs to be run before the PDF is processed</td></tr><tr><td>custom-link-import-configuration</td><td>No</td><td>string</td><td> </td><td>The configuration XML for the custom link import to be executed. This should only be used if instructed to do so by iPaper</td></tr><tr><td>custom-link-import-name</td><td>No</td><td>string</td><td> </td><td>The name of the enrichment automation configuration that should be used when processing the PDF.</td></tr></tbody></table>

**NB!:** xmlOptions should be wrapped in a root node `<options />` like shown in the sample below.

```xml
<options><preserve-links>true</preserve-links><import-bookmarks>true</import-bookmarks></options>
```

## Sample Response

<pre class="language-xml"><code class="lang-xml">&#x3C;?xml version="1.0" encoding="utf-8"?>
&#x3C;result>
  &#x3C;code>
    &#x3C;![CDATA[SUCCESS]]>
  &#x3C;/code>
  &#x3C;message>
    &#x3C;![CDATA[Process queued successfully.]]>
  &#x3C;/message>
  &#x3C;data>
    &#x3C;JobGuid>
      &#x3C;![CDATA[ 05d5ef54-6667-47f7-97c3-41bec10438d7 ]]>
    &#x3C;/JobGuid>
<strong>  &#x3C;/data>
</strong>&#x3C;/result>
</code></pre>

## Return Codes

* SUCCESS
* ERROR
* RIPPER\_CANNOT\_PROCESS\_PAPER\_ALREADY\_BEING\_PROCESSED
* RIPPER\_FILE\_IS\_NOT\_PDF
* RIPPER\_INVALID\_BASE64\_PDF\_FILE
* RIPPER\_PDF\_FILE\_DOES\_NOT\_EXIST
* LICENSE\_DOES\_NOT\_ALLOW\_PROCESSING


# Search

{% content-ref url="/pages/WGuP3LtwBa20Biiutr9l" %}
[SearchPapersByContent](/flipbook-backend-api/search/search-papers-by-content)
{% endcontent-ref %}

{% content-ref url="/pages/pw9bfg4DQeVy4esViyNM" %}
[SearchPapersByCustomFields](/flipbook-backend-api/search/search-papers-by-custom-fields)
{% endcontent-ref %}

{% content-ref url="/pages/bu2vZzQ7kDzrviFOrAoK" %}
[SearchPapersWithHiearchyByContent](/flipbook-backend-api/search/search-papers-with-hierarchy-by-content)
{% endcontent-ref %}

{% content-ref url="/pages/3p5QWMH4LOPfqQwbJYW6" %}
[SearchPapersWithHiearchyByName](/flipbook-backend-api/search/search-papers-with-hierarchy-by-name)
{% endcontent-ref %}


# SearchPapersByContent

## Description

Searches flipbooks recursively for text content on the indexed pages.

## Parameters

| Name              | Value                                                                                                       |
| ----------------- | ----------------------------------------------------------------------------------------------------------- |
| query             | The text string to search for. Max length: 128 chars                                                        |
| categoryOrPaperID | Either a PaperID or a CategoryID that should be searched. Will search recursively if CategoryID is provided |
| xmlOptions        | Not currently used                                                                                          |

## Sample Response

```xml
<?xml version="1.0" encoding="utf-8"?>
<result>
  <code><![CDATA[SUCCESS]]></code>
  <message><![CDATA[Search results returned.]]></message>
  <data>
    <results>
      <result paperID="1213" pageNumber="4" paperName="Test Catalog" paperUrl="/Demos/TestCatalog/" />
      <result paperID="1213" pageNumber="5" paperName="Test Catalog" paperUrl="/Demos/TestCatalog/" />
    </results>
  </data>
</result>
```

## Return Codes

* SUCCESS
* ERROR
* LOGIN\_SECURITY\_VIOLATION
* SEARCH\_TEMPORARILY\_UNAVAILABLE


# SearchPapersByCustomFields

## Description

Searches flipbooks by custom field values.

## Parameters

| Name       | Value                                                                            |
| ---------- | -------------------------------------------------------------------------------- |
| categoryID | The category whose descendants should be searched                                |
| xmlOptions | The xmlOptions parameter is used for specifying the search options in XML format |

### xmlOptions

* There must be at least one criteria.
* Criteria field values are automatically returned in the customfields section for result papers.
* Any custom fields that should be returned, and are not part of a criteria, should be added to the includedcustomfields section of the options.
* Each custom field criteria will have its value validated according to the field datatype.

#### **Sample Options**

<table><thead><tr><th>Element</th><th width="133">Required</th><th>Value</th></tr></thead><tbody><tr><td>customfieldcriterias</td><td>Yes</td><td>The usewildcards attribute is optional, default is false. Must only be specified for string type custom fields. Wildcard searches can be performed, using % as wildcard character</td></tr><tr><td>includedcustomfields</td><td>No</td><td>The include all attribute is optional, default is false. Returns all custom field values for the Flipbook. Use with care as amount of data may increase if many fields are defined</td></tr></tbody></table>

```xml
<options>
    <customfieldcriterias>
        <field name="[CriteriaFieldName]" usewildcards="[true/false]" value="[Value]" />
        ...
    </customfieldcriterias>
    <includedcustomfields includeall="[true/false]">
        <field name="[IncludedFieldName]"/>
        ...
    </includedcustomfields>
</options>
```

## Sample Response

{% hint style="info" %}
**Note:**

The lastprocessed field is only included if the Flipbook has been processed. Date is in 24H format.
{% endhint %}

{% hint style="info" %}
**Note:**

The pdffilesize field is only included if the Flipbook has been processed. File size is specified in KB.
{% endhint %}

```xml
<?xml version="1.0" encoding="utf-8"?>
<result>
  <code><![CDATA[SUCCESS]]></code>
  <message><![CDATA[Search results returned.]]></message>
  <data>
    <papers>
      <paper id="1120" name="Dot_Matrix_Range" url="/demos/DotMatrixRange/" lastprocessed="2009-03-15 15:27:00" pdffilesize="1357">
        <customfields>
          <field name="TestField" type="int" null="True"><![CDATA[]]></field>
          <field name="date" type="datetime" null="False"><![CDATA[2009-07-25]]></field>
        </customfields>
      </paper>
      ...
    </papers>
  </data>
</result>
```

## Return Codes

* SUCCESS
* ERROR
* SEARCH\_CUSTOMFIELD\_DATATYPE\_DOES\_NOT\_SUPPORT\_WILDCARDS
* SEARCH\_NO\_CUSTOM\_FIELD\_CRITERIAS
* SEARCH\_TEMPORARILY\_UNAVAILABLE
* PAPER\_DOES\_NOT\_EXIST
* PAPER\_MUST\_BE\_CATEGORY
* PAPER\_MUST\_NOT\_BE\_CATEGORY


# SearchPapersWithHiearchyByContent

## Description

Searches Flipbooks recursively for text content on the indexed pages and return Flipbooks with hiearchy information.

## Parameters

| Name       | Value                                                           |
| ---------- | --------------------------------------------------------------- |
| query      | The text string to search for. Max length: 128 chars            |
| categoryID | The CategoryID that should be searched. Will search recursively |
| xmlOptions | Not currently used                                              |

## Sample Response

```xml
<?xml version="1.0" encoding="utf-8"?>
<result>
  <code><![CDATA[SUCCESS]]></code>
  <message><![CDATA[Search results returned.]]></message>
  <data>
    <papers>
      <paper paperID="4984" name="MyPaper" url="/Client1/MyPaper/" parentID="3629" category="False" />
      <paper paperID="4985" name="Category1" url="/Client1/Category1/" parentID="3629" category="True" />
      <paper paperID="4986" name="NewPaper" url="/Client/Category1/NewPaper/" parentID="4985" category="False" />
    </papers>
  </data>
</result>
```

## Return Codes

* SUCCESS
* ERROR
* LOGIN\_SECURITY\_VIOLATION
* SEARCH\_TEMPORARILY\_UNAVAILABLE


# SearchPapersWithHiearchyByName

## Description

Searches Flipbooks recursively by name and returns Flipbooks with hiearchy information.

## Parameters

| Name       | Value                                                           |
| ---------- | --------------------------------------------------------------- |
| query      | The text string to search for. Max length: 128 chars            |
| categoryID | The CategoryID that should be searched. Will search recursively |
| xmlOptions | Not currently used                                              |

## Sample Response

```xml
<?xml version="1.0" encoding="utf-8"?>
<result>
  <code><![CDATA[SUCCESS]]></code>
  <message><![CDATA[Search results returned.]]></message>
  <data>
    <papers>
      <paper paperID="4984" name="MyPaper" url="/Client1/MyPaper/" parentID="3629" category="False" />
      <paper paperID="4985" name="Category1" url="/Client1/Category1/" parentID="3629" category="True" />
      <paper paperID="4986" name="NewPaper" url="/Client/Category1/NewPaper/" parentID="4985" category="False" />
    </papers>
  </data>
</result>
```

## Return Codes

* SUCCESS
* ERROR
* LOGIN\_SECURITY\_VIOLATION
* SEARCH\_TEMPORARILY\_UNAVAILABLE


# Statistics

{% content-ref url="/pages/bzLk6HGMzxFITd3GLdBg" %}
[GetNumberOfPageviewsPerDay](/flipbook-backend-api/statistics/get-number-of-page-views-per-day)
{% endcontent-ref %}

{% content-ref url="/pages/aDSs3OHg4cz5wUol5jjN" %}
[GetNumberOfVisitors](/flipbook-backend-api/statistics/get-number-of-visitors)
{% endcontent-ref %}

{% content-ref url="/pages/p35tHBTf1CyF4sqLgAl3" %}
[GetNumberOfVisitorsPerDay](/flipbook-backend-api/statistics/get-number-of-visitors-per-day)
{% endcontent-ref %}


# GetNumberOfPageviewsPerDay

## Description

Returns the number of pageviews per day, for a Flipbook, in a given time period. Dates returned in the response are the same time zone as the one present in your flipbook.

## Parameters

| Name    | Value                                                 |
| ------- | ----------------------------------------------------- |
| paperID | The ID of the Flipbook to retrieve data from          |
| from    | When should data be returned from? Format: YYYY-MM-DD |
| to      | When should data be returned to? Format: YYYY-MM-DD   |

## Sample Response

```xml
<?xml version="1.0" encoding="utf-8"?>
<result>
  <code><![CDATA[SUCCESS]]></code>
  <message />
  <data>
    <pageviews>
      <day date="2012-01-28" views="0" />
      <day date="2012-01-29" views="0" />
      <day date="2012-01-30" views="0" />
      <day date="2012-01-31" views="0" />
      <day date="2012-02-01" views="0" />
      <day date="2012-02-02" views="16" />
      <day date="2012-02-03" views="0" />
      <day date="2012-02-04" views="0" />
      <day date="2012-02-05" views="0" />
    </pageviews>
  </data>
</result>
```

## Return Codes

* SUCCESS
* ERROR
* STATISTICS\_TEMPORARILY\_UNAVAILABLE


# GetNumberOfVisitors

## Description

Returns the number of visitors (based on sessions, not unique visitors) for a Flipbook in a given time period.

## Parameters

| Name    | Value                                                 |
| ------- | ----------------------------------------------------- |
| paperID | The ID of the Flipbook to retrieve data from          |
| from    | When should data be returned from? Format: YYYY-MM-DD |
| to      | When should data be returned to? Format: YYYY-MM-DD   |

## Sample Response

```xml
<?xml version="1.0" encoding="utf-8"?>
<result>
  <code><![CDATA[SUCCESS]]></code>
  <message><![CDATA[Visitor count returned.]]></message>
  <data>
    <visitors count="8" />
  </data>
</result>
```

## Return Codes

* SUCCESS
* ERROR
* STATISTICS\_TEMPORARILY\_UNAVAILABLE


# GetNumberOfVisitorsPerDay

## Description

Returns the number of visitors (based on sessions, not unique visitors) per day, for a Flipbook, in a given time period. Dates returned in the response are the same time zone as the one present in your flipbook.

## Parameters

| Name    | Value                                                 |
| ------- | ----------------------------------------------------- |
| paperID | The ID of the Flipbook to retrieve data from          |
| from    | When should data be returned from? Format: YYYY-MM-DD |
| to      | When should data be returned to? Format: YYYY-MM-DD   |

## Sample Response

```xml
<?xml version="1.0" encoding="utf-8"?>
<result>
  <code><![CDATA[SUCCESS]]></code>
  <message><![CDATA[Visitor count returned.]]></message>
  <data>
    <visitors>
      <day date="2012-08-15" visitors="47" />
      <day date="2012-08-16" visitors="150" />
      <day date="2012-08-17" visitors="158" />
      <day date="2012-08-18" visitors="155" />
      <day date="2012-08-19" visitors="126" />
      <day date="2012-08-20" visitors="147" />
      <day date="2012-08-21" visitors="146" />
      <day date="2012-08-22" visitors="71" />
      <day date="2012-08-23" visitors="0" />
      <day date="2012-08-24" visitors="0" />
      <day date="2012-08-25" visitors="0" />
    </visitors>
  </data>
</result>
```

## Return Codes

* SUCCESS
* ERROR
* STATISTICS\_TEMPORARILY\_UNAVAILABLE


# Popup Content

## Sizing

The content of the iframe reflows, so no scale is applied.

## Images

Large images automatically shrink to iframe width.

## Talking to the iPaper API

You cannot communicate with the iPaper API.

## Rotation/resizing

The popup content wrapper has the following constraints:

```css
max-width:	600px;
min-width: 	200px;
min-height:	250px;
```

Content wrapper will resize itself, to fit inside the browser window, until it reaches a max/min width. Height is set based on content height, and window height. When content is taller than window, content will scroll. When the window is taller than the content, the content wrapper will set height based on content height.

## CSS applied

```css
html, body, h1, h2, h3, p {
    margin: 0;
    padding: 0;
    font-family: Verdana, Arial, Sans-Serif;
}
body {
    padding: 10px;
    -webkit-transform-style: preserve-3d;
    -webkit-text-size-adjust: 100%;
}
p {
    line-height: 20px;
    margin-bottom: 10px;
    font-size: 14px;
}
hr {
    border: none;
    border-top: 1px solid #CCC;
    margin-top: 12px;
    display: block;
    height: 10px;
}
img {
    max-width: 100%;
    height: auto !important;
}
```


# Popup Frame

## Security

The best precautions security-wise is to include both X-Frame-Options and Content-Security-Policy for full browser support. Read more about how this is done below.

### Security headers

To ensure that your content is safeguarded against [clickjacking](https://owasp.org/www-community/attacks/Clickjacking) we recommend using the HTTP headers, X-Frame-Options and Content-Security-Policy. In the case you have added preventive measures we check the embedded website before trying to serve the popup-frame in the Flipbook viewer. This ensures that we don’t end up showing the enduser an error in the browser, if this site doesn’t allow to be iframed we give the user an option to open the site in a new tab in the browser.

### X-Frame-Options

Due to the nature of `ALLOW-FROM` being deprecated in the major browsers this is no longer a viable solution for preventing clickjacking in flipbooks. To solve that issue you will have to refer to Content-Security-Policy.

### Content-Security-Policy

Applying Content-Security-Policy is a more complex task, as it can have implications on the content on your own site if not done correctly. If your only intent is to prevent clickjacking, the frame-ancestors directive can be applied without it having implications on your content. In the case of you not having a branded domain you should add `frame-ancestors viewer.ipaper.io` and in the case of you using a branded domain then you should of course use your branded domain instead.

As of the specification for Content-Security-Policy version 2 and 3 the value of frame-ancestors override any value set in X-Frame-Options if those specifications have been implemented by the browser. For further information about Content-Security-Policy click here [MDN](https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP).

## Closing a popup from the frame page

It is possible for a frame page to close its popup. To do this, you simply have to execute the following JavaScript in the page that is framed.

```javascript
parent.postMessage('closePopup', '*');
```

If you want, you can pass additional parameters to the close command to update the basket count of the basket icon in the flipbook, as well as displaying the product name of the product that was added.

```javascript
parent.postMessage({ 
    command: 'closePopup',
    productName: 'White lamp', /*optional*/
    basketCount: 3 /* optional */
}, '*');
```

## Workarounds for iOS browser caveats

These workarounds apply to all browsers on iOS (namely Safari and any other third-party browsers), because they all use the same WebKit rendering engine on iOS.

### Iframed page jumping to top

Certain pages may occasionally jump to the top when iframed within a popup frame. It is often triggered by repaint events in the browser that involve changing the height of the page, as well as on pages that are excessively large. It is probably due to the iOS browser not being able to calculate the actual height of the `<iframe>` element properly.

To circumvent this issue, we recommend adding the following styles to the stylesheet of the page that is embedded in the popup frame:

```css
html, body {
    height: 100%;
    overflow: auto;
    -webkit-overflow-scrolling: touch;
}
```

### Page in popup frame appears too wide

Depends on how a page’s width is set in the stylesheet, there is a possibility that certain pages, when embedded within an `<iframe>` and viewed on iOS browsers, will appear too large and hence overflow from its original bounds.

In iOS browsers, `<iframe>` elements cannot be restricted by size: declaring explicit width and height in CSS does not work. The browser will instead attempt to size the `<iframe>` so that all its content becomes visible. This has proven to be problematic for a small subset of pages, especially those containing wide content that are dynamically resized by JavaScript.

We recommend adding `max-width: 100vw` to the body element of the embedded page, which will force the `<iframe>` to be aware of the size of its wrapping container. It effectively places a constraint on the maximum permissible width for the page:

```css
body {
    max-width: 100vw;
}
```

## See also

[OWASP Guide to Clickjacking defense](https://cheatsheetseries.owasp.org/cheatsheets/Clickjacking_Defense_Cheat_Sheet.html)


# Custom Domain

## Configuration

Before iPaper can setup the Custom Domain module, you need to setup the domain on your end first. The subdomain can be anything you want, but let’s assume the following scenario:

```
Current catalog address: viewer.ipaper.io
Main website: www.client.com
Subdomain: catalogs.client.com
```

In this case, catalogs.client.com needs to be setup as a CNAME record pointing to the partner address. That is, the following CNAME record should be made:

```
catalogs.client.com => viewer.ipaper.io
```

If your catalogs are hosted through an iPaper partner, the CNAME alias should point to that specific partners address:

```
catalogs.client.com => {partner}.ipapercms.dk
```

{% hint style="warning" %}
**Important:**&#x20;

The subdomain MUST be setup as a CNAME record, an A record will not work. If you have both External and Internal DNS, the CNAME record should be set up on both.
{% endhint %}

## Verification

To verify if the domain has been setup, you can use the following tool: <http://network-tools.com/nslook/Default.asp?domain=publikationer.ipaper.dk&type=255&server=67.222.132.198&class=1&port=53&timeout=5000&go.x=-791&go.y=-257>

Replace `publikationer.ipaper.dk` with the desired subdomain.


# Custom Mail

## Configuration

Before custom mail can be setup in iPaper, you need to setup the SPF record on your domain first.

If you do not have an SPF record yet, you can add the following as a TXT record on the domain you wish for us to send of behalf of.

```
v=spf1 include:spf.ipaper.io ~all
```

In the case you already have an SPF record in your DNS configuration you should only add `include:spf.ipaper.io`, else you risk loosing emails that was previously configured via your SPF record.

{% hint style="warning" %}
**Important:**&#x20;

Be aware if you already have an SPF record setup in your DNS, please do ensure you are only adding the partial record
{% endhint %}

## Verification

To verify if the SPF record have been setup correctly you can use the following tool:

<https://mxtoolbox.com/SuperTool.aspx?action=spf%3aipaper.io&run=toolpage>

Replace “ipaper.io” with the desired domain.

If `spf.ipaper.io` appears in the list of results with a pass in `PrefixDesc` you should be all good.


# Embedding flipbooks inside iOS and Android App

{% hint style="warning" %}
**Important:**

If you in any way exploit a quirk of our system to your benefit then we cannot guarantee that our flipbooks will continue to work as you would expect. If you feel the need to make any kind of workaround to achieve your desired behavior, make sure you validate it with us first. Otherwise you risk your app breaking at some point when we update our system.\
\
If you get in contact with us first, we’ll do all we can to help you out and ensure a long term viable solution is made.
{% endhint %}

## Embedding the flipbook

iPaper will automatically detect which device is trying to access any given flipbook and display corresponding view. Whether that be the iPad, iPhone or Android views for handheld devices.

You should always link directly to a flipbook of the form:

```
http://partner.ipapercms.dk/Firma/Tilbudsavis/
```

{% hint style="warning" %}
**Important:**

It is very important that you never link directly to any files/subdirectories beneath the flipbook - only the main flipbook url can be trusted to be static.
{% endhint %}

{% hint style="warning" %}
**Important:**

If you are embedding your Flipbook in an iOS app and the Flipbook is utilizing autoplay videos, you should ensure that the iOS app [`allowsInlineMediaPlayback`](https://developer.apple.com/documentation/webkit/wkwebviewconfiguration/1614793-allowsinlinemediaplayback?language=objc) property is set to `true`. By default, this property will be `false` on iPhones, which causes autoplay videos to open in full-screen which can lead to a confusing/bad user experience when browsing the Flipbook.
{% endhint %}

## Important query string parameter when embedding the flipbook

For some mobile devices, we can’t detect whether the navigation bar is present or not. As a result, content may either be shown too small, or cropped, in such cases.

To avoid this, you should hard code the browser height and send it to us in the query string like so:

```
http://partner.ipapercms.dk/Firma/tilbudsavis/?EmbeddedHeight=x
```

In the example above we will force our content to be exactly `x` pixels high, circumventing our normal size calculations.

{% hint style="info" %}
**Note:**

Make sure that the EmbeddedHeight value is an integer with no decimals.
{% endhint %}

{% hint style="warning" %}
**Important:**

Unfortunately it is not possible to dynamically resize the embedded flipbook when using the EmbeddedHeight parameter. As such, you’ll have to resize the container and reload the catalog with a new EmbeddedHeight value appended.
{% endhint %}

### Calculating the EmbeddedHeight value on iOS devices

On iOS, you can calculate the EmbeddedHeight value of a given UIWebView like this:

```
CGRect webViewSize = [_webView bounds];
int embeddedHeight = (int)webViewSize.size.height;
```

### Calculating the EmbeddedHeight value on Android devices

On Android you can calculate the EmbeddedHeight value of a given WebView like this:

```
CGRect webViewSize = [_webView bounds];
int embeddedHeight = (int)webViewSize.size.height;
```

## Shop Integration

When embedding a flipbook inside an iOS/Android application it is not possible to use our normal JavaScript API, as this requires the flipbook to be iframed. As an alternative it is possible to override the normal behavior of shop links and force them to open a specially formed URL, enabling you to intercept the call and thus be notified of users clicking on shop links.

To force the special shop link functionality, you’ll have to add a special query string parameter to the flipbook URL. If the normal flipbook URL is this:

```
http://ipaper.ipapercms.dk/client/MyCustomFlipbook/
```

The embedded URL should be this, to invoke the special shop link handling:

```
http://ipaper.ipapercms.dk/client/MyCustomFlipbook/?ExternalJsonShopLinks=True
```

Once the `ExternalJsonShopLinks` parameter has been enabled, clicking on a shop link will then navigate the user to the following URL, containing a JSON object with all of the product details. The JSON object/payload is encoded using UTF-8:

```
ipapershop://<payload>
```

| Property      | Type     | Description                                |
| ------------- | -------- | ------------------------------------------ |
| `title`       | `string` | Shop item title                            |
| `description` | `string` | Shop item description                      |
| `productId`   | `string` | Shop item product ID                       |
| `price`       | `number` | Shop item price                            |
| `originPage`  | `number` | Page number where the shop item is located |
| `quantity`    | `number` | Number of items added                      |
| `packageSize` | `number` | Number of items per package                |

An example of the emitted JSON is as follow:

```json
ipapershop: {
	title: "Milk",
	description: "Half gallon",
	productId: "M-926",
	price: 25.95,
	originPage: 4,
	quantity: 2,
	packageSize: 1
}
```

{% hint style="info" %}
**Note:**

This functionality is only applicable to the mobile versions, and setting the `ExternalJsonShopLinks` parameter thus has no impact on the normal desktop version of the flipbook.
{% endhint %}


# Form Integration

## Configuration

When using the Form module, you have to ability enable POST integration. With this setting enabled, when the users clicks the “Submit” button in the Form, the values entered by the user will be sent via a POST request to the endpoint specified under the Form Settings.

The setting needed for enabling POST integration, is shown below:

<figure><img src="https://3504352165-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FW0UCgeNLp5UrqOZTjvsN%2Fuploads%2Fpsg9Dx0OWdJ0TZE8WVdW%2Fform-integration-1.PNG?alt=media&amp;token=b971232c-6c09-4cfa-ad0f-24ba43588b6e" alt=""><figcaption></figcaption></figure>

## Receiving data from Form POST integration

The values received by the endpoint, will come in form of standard Web Form values. Each value will have a key corresponding the the “Name” value of the form element. Looking at the overview of your Form, you will a list of all Name of all form fields.

{% hint style="warning" %}
**Important:**

Please note that the “Name” value is an internal name not visible by the end-user, as not to confuse with the “Label” value
{% endhint %}

For the form in the figure below, the data received by the endpoint would be:

```
Name=Value&Address=Value&Email=Value
```

<figure><img src="https://3504352165-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FW0UCgeNLp5UrqOZTjvsN%2Fuploads%2FGpLsyJius9hmBv689u7L%2Fform-integration-2.PNG?alt=media&amp;token=bc6e8d4d-27d2-49a3-9f0a-3c4fa0666163" alt=""><figcaption></figcaption></figure>


# Iframing Flipbooks

{% hint style="warning" %}
**Important:**

While it is possible to do, we generally do not recommend iframing catalogs unless you have a specific reason for doing so.

* As visitors come from a multitude of devices, including tablets, phones, and desktops, you’ll have to handle the different screen sizes yourself.
* Tablets and phones have special requirements to ensure they work when users rotate their device.
* The smaller the catalog is, the harder it is for users to read it. By opening up catalogs in a new window, rather than in an iframe, you ensure the users get the best possible reading experience.

This being said, if you want or need to embed the flipbook in an `<iframe>` element, please make sure to read and understand the following guide carefully.
{% endhint %}

## A brief note on domains

For best results, the flipbook you wish to embed should exist on a subdomain to the domain on which the iframe is placed. A [working Branded Domain module](https://help.ipaper.io/en/articles/1857338-how-can-i-show-flipbooks-on-my-own-domain-branded-domain) is required for this. If you embed the flipbook using the iPaper-branded URL, we cannot offer support for it and weird cross-device behavior can be expected.

## Foreword

When using iframes on mobile devices it can be tricky trying to make it respond as expected. Below we show you how to make flipbooks behave optimally.

{% hint style="warning" %}
**Important:**

It’s important that you follow our directions below precisely. There are many devices and what may work for you in testing will fail for users in production. If you manage to get something working that isn’t described here, do tell us so we can verify the solution. Anything but the below is to be considered unsupported and may fail either now or in the future.
{% endhint %}

## Required permissions

### Autoplaying and fullscreen access in video enrichments

In order to allow elements in the flipbook to do the following when it is iframed:

1. Autoplay videos without hindrance, and
2. Have access to the fullscreen API (e.g. embedded videos from Vimeo/YouTube or native MP4 videos)

…the `<iframe>` element needs to allow these explicitly by means of specifying a [feature policy](https://developer.mozilla.org/en-US/docs/Web/HTTP/Feature_Policy). This is done by adding the following attributes to the element:

* `allow="autoplay; fullscreen;"`
* `allowfullscreen` (for backwards compatibility)
* `webkitallowfullscreen` (for backwards compatibility)
* `mozallowfullscreen` (for backwards compatibility)

### Clipboard and share

Flipbooks use native browser APIs for sharing and copying URLs. Those APIs require extra permissions to be granted on iframes:

* `allow="web-share;"` gives the iframe a permission to use the native sharing functionality provided by the visitor's OS
* `allow="clipboard-write;"` gives the iframe a permission to write data into the visitors clipboard

### Iframe tag with all required permissions

Your `<iframe>` source will look a little like this, with `YOUR_FLIPBOOK_URL` replaced with the absolute URL of the flipbook you want to embed on your page:

```html
<iframe
  src="YOUR_FLIPBOOK_URL"
  allow="autoplay; fullscreen; web-share; clipboard-write;"
  allowfullscreen
  webkitallowfullscreen
  mozallowfullscreen></iframe>
```

## Embedding flipbooks in specific sizes <a href="#embedding-flipbooks-in-specific-sizes" id="embedding-flipbooks-in-specific-sizes"></a>

### 100%×100% size (full viewport size)

{% hint style="info" %}
**Note:**&#x20;

This is supported on all platforms and is the recommended way of iframing catalogs.
{% endhint %}

In order to iframe a iPaper catalog, spanning 100% of page, we need to use a specific setup. The markup below shows the essence of what is needed. Firstly, if we set the `<iframe>` element to have the width and height of 100% we must place this inside a div. Having done this we need to do the following:

* Doctype must be `<!DOCTYPE html>`
* Set the wrapper `<div>` width and height to 100%.
* Set the wrapper `<div>` overflow to hidden.
* Set `<body>` and `<html>` height to 100%.
* Include the `<meta>` tag presented below.
* Include the `<script>` tag presented below, setting the document.domain to the root domain used.

{% hint style="warning" %}
**Important:**

The steps listed above must be ready on load. It will NOT work if you manipulate the DOM client-side.
{% endhint %}

```html
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" style="height: 100%">
  <head>
    <title>Catalogues</title>
    <style type="text/css">
        * { margin: 0; padding: 0; }
    </style>
    <script type="text/javascript">document.domain = 'ipaper.io';</script>
    <meta name="viewport" content="initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no, width=device-width" />
  </head>
    <body style="height: 100%">
        <div style="height: 100%; width: 100%; overflow: hidden">
            <iframe
                src="http://test.ipapercms.dk/Releases/Mobile/"
                scrolling="no"
                frameborder="0"
                style="width: 100%; height: 100%"
                allow="autoplay; fullscreen;"
                allowfullscreen
                webkitallowfullscreen
                mozallowfullscreen></iframe>
        </div>
    </body>
</html>
```

### Non-100%×100% size <a href="#non-100100-size" id="non-100100-size"></a>

{% hint style="warning" %}
**Important:**

This is only supported for the **iPad** and **Desktop** platforms. On all other platforms it is unsupported. While you may get it to work, expect it to break on certain devices and later on when we change the system.
{% endhint %}

For these platforms you can specify width in any size you want, even in percentages. Do take care to leave enough space for the user to have a usable experience however. To set the size, you need to change the dimensions on the outer div like this:

```html
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" style="height: 100%">
  <head>
    <title>Catalogues</title>
    <style type="text/css">
        * { margin: 0; padding: 0; }
    </style>
    <script type="text/javascript">document.domain = 'ipapercms.dk';</script>
    <meta name="viewport" content="initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no, width=device-width" />
  </head>
    <body style="height: 100%">
        <div style="height: 300px; width: 600px; overflow: hidden">
            <iframe
                src="http://test.ipapercms.dk/Releases/Mobile/"
                scrolling="no"
                frameborder="0"
                style="width: 100%; height: 100%"
                allow="autoplay; fullscreen;"
                allowfullscreen
                webkitallowfullscreen
                mozallowfullscreen></iframe>
        </div>
    </body>
</html>
```

### Dynamically Resizing

Dynamically resizing the div will work. Do make sure you resize the outer element and not the iframe.

### Removing the iPaper User Interface

To remove the iPaper user interface, go to **Settings > General > Interface > Hide top bar**.\
Or have a look at [Query String Parameters](/flipbook-integration/query-string-parameters#hidestandardui)

{% hint style="info" %}
**Note:**

All JavaScript events will still be fired, even without the user interface present.
{% endhint %}

## Troubleshooting

### There is unexpected whitespace around my flipbook

**Symptoms:** There is unexpected whitespace around the flipbook (either vertically or horizontally). When the viewport is resized to a smaller size, the padding proportionally decreases.

**Common causes:** The containing element of the `<iframe>` element or the `<iframe>` element itself has an explicit width that is too large compared to the maximum size we render the flipbook as.

**Solution:** Set a `max-width` or `max-height` property on the containing element of the `<iframe>` element to a specific value (for instance `720px`). Experiment with different values that work well on exactly your website and different window sizes.


# iPaper Embed Script (Pop-ups)

{% hint style="info" %}
The iPaper embed script snippet is a small piece of JavaScript that you paste in to your webpages. It enables the iPaper service to inject Pop-ups into the pages where the embed script is present.
{% endhint %}

## Find your account specific embed script

1. Log in to your iPaper account.
2. Navigate to → ‘Pop-ups’.
3. Click the ‘Get embed script’ button.
4. Click ‘Copy script’ to copy the embed script to the clipboard.

<figure><img src="https://3504352165-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FW0UCgeNLp5UrqOZTjvsN%2Fuploads%2FQ5xuVLB82SZsOw5QsHLW%2Fipaper-embed-script.PNG?alt=media&amp;token=aab7b35a-db9c-43ec-8f64-d283004781ee" alt=""><figcaption><p>The embed script modal</p></figcaption></figure>

## Insert snippet on your website

The embed script should be inserted as the last element of the `<body>` tag on every webpage where you want Pop-ups to show. A typical use case would be to add the embed script to all pages on your website.

{% hint style="info" %}
**Note:**

Browser loads webpages progressively, which means that the resources are loaded in order of their occurrence in the source code. It is a good practice to place nonessential scripts as the last elements of the webpage
{% endhint %}

<figure><img src="https://3504352165-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FW0UCgeNLp5UrqOZTjvsN%2Fuploads%2F4AkGrjQn7lQB84Elarft%2Fipaper-embed-script-2.PNG?alt=media&amp;token=0ef79b14-a405-4723-a39d-cb030e564719" alt=""><figcaption></figcaption></figure>

{% hint style="info" %}
**Note:**

You don’t need the embed script when adding Pop-ups to your Flipbooks
{% endhint %}

## After you have added the embed script

Once the embed script is added to your website, you will start to see Pop-ups that match the URL you have defined.

## Final notes on embed script

### Same embed script for different Pop-ups under one license

Embed script are unique per license (login group). This means that all Pop-ups created under the same license will share the same embed script code. Therefore, there is no need to insert embed script with given API\_KEY on one webpage (URL) more than once, even when there is more than one lead generation tool that should be displayed on that page.

{% hint style="info" %}
**Note:**

You don’t need the embed script when adding Pop-ups to your Flipbooks
{% endhint %}

### Multiple embed scripts on same webpage

In some edge case scenarios there might be a reason to use different embed scripts (with unique API\_KEY) on the same webpage. This might be the case when users from different login groups are maintaining lead generation tools for the same webpage. In such cases the rules of inserting the code into webpage stays the same: embed scripts should always be inserted as the last elements of the `<body>` tag.


# Embeds (Minipapers)

## Description

Minipapers is a method for which to easily embed a miniaturized version of a flipbook on your website. The following view options are available:

* **Perspective**—displays a three-dimensional flipbook
* **Single page**—displays a single page on the flipbook
* **Spread**—displays a spread on the flipbook
* **Page slider**—displays an animated slider showing pages and spreads of a pre-defined page range

Once integrated on your website, clicking on a minipaper will redirect the user to the actual flipbook being displayed.

## Redirecting to an alternative URL

It is possible to override the default link action and target when a user clicks on the embedded minipaper. This behavior can be configured by appending relevant query strings to the URL found in the `src` attribute of the `<iframe>` element in the embed code. For example, given the following embed snippet:

```html
<!-- iPaper snippet start -->
<iframe
    style="display: block;"
    src="<MinipaperEmbedUrl>"
    scrolling="no"
    frameborder="0"></iframe>
<!-- iPaper snippet end -->
```

The URL to be modified will be the string `<MinipaperEmbedUrl>`. The following query strings are supported:

<table><thead><tr><th width="156">Key</th><th width="168">Accepted values</th><th width="165">Default value</th><th>Description</th></tr></thead><tbody><tr><td><code>targetUrl</code></td><td><code>string</code></td><td>The public URL of the flipbook</td><td>Changes the URL that users are redirected to when an embedded minipaper is clicked on. The URL must be fully qualified, starting with <code>http://</code> or <code>https://</code>, and be <a href="https://en.wikipedia.org/wiki/Percent-encoding">URL encoded</a>.<br><br>Example:<br><code>&#x3C;MinipaperEmbedUrl>?targetUrl=https%3A%2F%2Fipaper.io%2F</code></td></tr><tr><td><code>target</code></td><td><code>_self</code>, <code>_blank</code>, <code>_parent</code>, <code>_top</code></td><td><code>_blank</code></td><td>Changes the behavior of the anchor element that determines where to display the linked URL. <a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/a#target">Read more about the specification for <code>target</code> attribute</a>.<br><br>Example:<br><code>&#x3C;MinipaperEmbedUrl>?target=_self</code></td></tr></tbody></table>

If you want multiple query string keys to be attached to the URL, you can separate them using the `&` character, i.e.: `<MinipaperEmbedUrl>?targetUrl=<Url>&target=<Target>`. The order of the queries does not matter.

{% hint style="info" %}
**Note:**&#x20;

As per specification, **all values in query strings must be properly** [**URL-encoded**](https://en.wikipedia.org/wiki/Percent-encoding). There are several online tools for encoding text for use in query strings, for example: <https://meyerweb.com/eric/tools/dencoder/>.
{% endhint %}

## Preserving aspect ratio

If you want to preserve the aspect ratio of the embedded minipaper, so that it is scaled proportionately in a page with responsive design, we suggest using the [`padding-bottom` trick](https://css-tricks.com/aspect-ratio-boxes/), which will force an aspect ratio of your choice on the surrounding container. The element displaying the minipaper—the `<iframe>` element—should then be positioned absolutely relative to this container.

For example, if a 2:1 ratio is desired for a container, `padding-bottom: 50%` should be used:

```html
<div style="position: relative; width: 100%; padding-bottom: 50%;">
    <!-- iPaper snippet start -->
    <iframe
        style="display: block; width: 100%; height: 100%;"
        src="<MinipaperEmbedUrl>"
        scrolling="no"
        frameborder="0"></iframe>
    <!-- iPaper snippet end -->
</div>
```


# Query String Parameters

Certain functions in Flipbooks can be invoked by including query string parameters in the Flipbook address. The following parameters are documented and supported. Any parameter not documented here is not supported and shold not be used and depended upon.

## ForceLanguage

Say you have an international catalog with visitors from many nationalities. In iPaper CMS you can only define one language for the user interface (though this can be used to auto-detect the users language), but using the ForceLanguage parameter, you can override the normal user interface language. Example URL that forces the language to French:

```
http://catalogs.company.com/Brochure/?ForceLanguage=fr
```

{% hint style="info" %}
**Note:**

Please refer to iPaper Support if you need a specific language code. If you link to a non-existing language code, the catalog default will be used instead.
{% endhint %}

## GoToFirstSearchResult

If you do not want to link to a specific page number, but rather to the first page that contains a certain search term, the GoToFirstSearchResult parameter can be used. The following sample link would take the user to the first page containing the term “Century”:

```
http://catalogs.company.com/Brochure/?GoToFirstSearchResult=Century
```

{% hint style="info" %}
**Note:**

If the search term is not found, the user will be sent to the first page.
{% endhint %}

{% hint style="danger" %}
**Warning:**

If both `GoToFirstSearchResult` and [`GoToAndHighlightFirstSearchResult`](#gotoandhighlightfirstsearchresult) are specified in the URL, this query string value will be ignored.
{% endhint %}

## GoToAndHighlightFirstSearchResult

This query performs the same operation as [`GoToFirstSearchResult`](#gotofirstsearchresult) above, but has the additional ability to highlight the term on the page itself.

```
http://catalogs.company.com/Brochure/?GoToAndHighlightFirstSearchResult=Century
```

{% hint style="info" %}
**Note:**

If both [`GoToFirstSearchResult`](#gotofirstsearchresult) and `GoToAndHighlightFirstSearchResult` are specified in the URL, this query string value will take precedence.
{% endhint %}

## HideNavigationBars

Using this setting you can override the “Hide paging arrows” setting on Flipbooks. To set a default on a Flipbook go to **Settings > General > Interface > Hide paging arrows**.

```
http://catalogs.company.com/Brochure/?HideNavigationBars=true
```

{% hint style="info" %}
**Note:**

Users will still be able to flip the pages in the Flipbook by swiping the pages, they will just not be able to see or use the navigation buttons.
{% endhint %}

## HideStandardUI

Using this setting you can override the “Hide top bar” setting on Flipbooks. To set a default on a Flipbook go to **Settings > General > Interface > Hide top bar**. This setting will hide the top bar along with basket, search, menu, logo and anything else present in the bar.

```
http://catalogs.company.com/Brochure/?HideStandardUI=true
```

## Page

The Page parameter is used when you want to deeplink to a certain page in the catalog. The following sample link would take the user directly to page 15:

```
http://catalogs.company.com/Brochure/?Page=15
```

{% hint style="info" %}
**Note:**

Linking to a non-existing page number will take the user to the first page instead.
{% endhint %}

## Query string forwarding

To forward query strings to links, you can prefix query strings with `ipforward_`. We will strip the prefix and add your query strings to any outgoing links in the Flipbook.

As an example, the below URL:

```
http://catalogs.company.com/Brochure/?ipforward_YourParameter=x
```

Would result in external links getting decorated with your parameter: `YourParameter=x`

You can use the `ipforward_` prefix with any parameter you want. If someone follows a link within your Flipbook, the `ipforward_` is stripped, so only your parameter is retained in the link. And you can of course add multiple parameters:\
`?ipforward_YourParameter=x&ipforward_YourOtherParameter=y`

## ShouldEnableGoogleAnalyticsDebugMode

Enabling debug mode for a specific GA4 (Google Analytics 4) measurement ID will allow you to see each individual action happening live in Google Analytics debug view.

As an example, the below URL:

```
http://catalogs.company.com/Brochure/?ShouldEnableGoogleAnalyticsDebugMode=true
```


# Remote Authentication

Using the Remote Authentication module it’s possible to completely secure a Flipbook, while maintaining full control of access validation on the clients’ own systems.

{% hint style="warning" %}
**Important:**

Please note that iPaper does not support any 3rd party protocols such as OAuth or Facebook. If you are looking to integrate a SSO solution into your flipbook, you will need you create a custom solution using Token Based Authentication
{% endhint %}

## Authentication Types

There are two overall mechanisms that allows the user to be granted access to the flipbook.

### Credential Based Authentication

In this scenario, when the user tries to open a secured Flipbook, he/she is automatically redirected to our standard login page. On the login page, the user can enter his/her username & password. Once “Login” is clicked, the iPaper system contacts the clients’ system. Depending on the response from the clients’ system, the user is either denied or granted access.

### Token Based Authentication

In this scenario, the user is browsing on the clients’ secured website. Instead of having to login again to see the catalog, token based authentication can be used. When linking to the desired catalog, an arbitrary string (token) is sent along in the querystring. Once the catalog opens, iPaper will contact the clients’ system and provide the token back - at this point the clients’ system can verify that the user stems from there originally and hence grant or deny access.

## Implementing Token Based Authentication

For token based authentication the client system must expose a public url that the iPaper system will call to authenticate tokens. While not a requirement, we recommend that it’s exposed through HTTPS for optimal security. The url itself is specified through the “Authentication url” setting under the “Remote Authentication” module for either a Flipbook or a folder.

For token based authentication to be invoked, a catalog must be opened with a “RemoteAuthenticationToken” parameter in the querystring. Once a protected catalog is opened and a remote authentication token is present in the querystring, we will send a POST request to specified url, containing the following POST fields:

| POST Field         | Description                                                                                                                                |
| ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------ |
| CatalogUrl         | This is the url of the catalog - in case logins need to be verified according to the requested catalog. Example: /Company/Catalogs/Week13/ |
| UserIP             | This is the IP of the user trying to authenticate. Example: 212.242.193.110                                                                |
| AuthenticationMode | For credential based authentication this will always be Credentials                                                                        |
| Token              | This is the token as specified by the RemoteAuthenticationToken querystring parameter                                                      |

The response should be in the following format:

```
<RemoteAuthentication>
    <AccessAllowed>true</AccessAllowed>
</RemoteAuthentication>
```

AccessAllowed being a boolean specifying whether the user is granted or denied access to the requested catalog.

## Remote Authenticaition Settings

These are set on the Flipbooks(s) to be protected, just as the actual remote authentication service URL.

### Alternate External Login URL

When using token based authentication and the user visits a catalog directly, it might be desirable to redirect the user to the clients’ own website for logging in. If the “External login url” setting is set, the user will automatically be redirected here, instead of being shown the standard login page. At this point, the client site may perform a login, create a token and redirect the user back to the catalog. The original catalog url will be provided in the “CatalogUrl” querystring parameter.

### Alternate External Login Failure URL

In case of a login failure, it may be desirable to send the user to a custom login failure page. If the “External login failure url” setting is specified, the user will be sent here on login failures, instead of being shown the standard login failure page.

## Extra Response Options

Besides the AccessAllowed (true/false) response, there’s a number of extra results that may be returned.

### RedirectTo

In the authentication response, a RedirectTo parameter may be specified like so:

```
<RemoteAuthentication>
    <AccessAllowed>true</AccessAllowed>
    <RedirectTo>http://www.client.com/ExpiredLogin.aspx</RedirectTo>
</RemoteAuthentication>
```

If the RedirectTo parameter is set, the iPaper system will automatically redirect the user to the specified url. This may be useful to provide a user with user specific feedback in case of login failures, promotions etc. Note that the RedirectTo parameter will be utilized no matter if a positive or negative AccessAllowed response is returned. If a positive AccessAllowed response is returned, the user is granted access to the catalog before being sent on to the RedirectTo url. Thus the user can be sent back to the catalog without going through the authentication procedure again. The original catalog url will be provided in the `CatalogUrl` querystring parameter.

### GrantAccessToDescendants

In the authentication response, a GrantAccessToDescendants parameter may be specified like so:

```
<RemoteAuthentication>
    <AccessAllowed>true</AccessAllowed>
    <GrantAccessToDescendants>true/false</GrantAccessToDescendants>
</RemoteAuthentication>
```

If the GrantAccessToDescendants element is set to true, iPaper will automatically validate the user for any Flipbook that are either direct children or descendants of the current address.

Imagine the following structure:

* /MyFolder/
  * /MyFolder/A/
  * /MyFolder/B/
  * /MyFolder/C/

If you access A/B/C directly and validate, you will only be granted access to that specific catalog, even with the GrantAccessToDescendants parameter set to true. If you setup one of the A/B/C Flipbooks as the default Flipbook of /MyFolder/ and validate, the user will automatically be validated for any Flipbooks starting with /MyFolder/ - in effect, A/B/C. As such, visiting just the default Flipbook, can be used to validate the user for a whole structure of Flipbooks.

## Sample ASP.NET Implementation

The *RemoteAuthentication.zip* file contains two pages: Login.aspx and Authenticate.ashx

{% file src="/files/Xs0szCOy3WBtlPbpg8Ud" %}

Authenticate.ashx contains functionality for validating both credential and token based authentication requests, using a simple switch on the AuthenticationMode parameter. The credentials and token are hardcoded in the files, so it can be used as a template for performing a real validation.

Login.aspx is used for testing out token based authentication with a remote login page. The following workflow shows the functionality:

1. User attempts to open a protected catalog.
2. User is sent to the remote login url (Login.aspx).
3. Once user validates, Login.aspx redirects the user back to the original catalog, alongside a RemoteAuthenticationToken in querystring.
4. iPaper contacts the authentication page and attempts to validate the provided token.
5. Given a positive result, the user now has access to the catalog.


# Shop Export

Using shop configuration it is possible to let the user add a number of items to the shopping basket in the catalog, after which the contents can be sent to a remote site for further processing. At this point, the remote site can handle registration, payment, etc.

## Setup

Once the shop configuration has been enabled, you can enable export by selecting “Export” as the primary checkout option. Once enabled, you will have to provide an export URL. This is the URL to which the shop contents are sent, when the user invokes the shop export function. Finally, you can choose whether to send the data in the current window, or open it in a new window.

## Export format

To assist in keeping track of orders sent through our Shop Export we supply a few more details than just the items that was ordered.

| Property Name       | Description                                                                                               |
| ------------------- | --------------------------------------------------------------------------------------------------------- |
| paper               | Short hand URL from where the flipbook was served                                                         |
| flipbookcompleteurl | Full URL of the flipbook                                                                                  |
| exportorigin        | Full URL of the flipbook, including the querystring that was set at the point in time the export was done |
| orderId             | Order Id in the iPaper platform that can be used for reference                                            |
| orderDate           | Date when the order was exported from the iPaper platform                                                 |

For each item in the basket that was exported we include the name, description, price, product ID, and amount, as selected in the catalog. An example of a shop XML is as follow:

```xml
<shop paper="/url/of/your/flipbook/" flipbookcompleteurl="https://public/url/of/your/flipbook/" exportorigin="https://public/url/of/your/flipbook/?page=3" orderId="R2xV0s" orderDate="2020-07-13T06:42:40Z">
    <item>
        <amount>2</amount>
        <productid><![CDATA[AB123]]></productid>
        <price>99.95</price>
        <name><![CDATA[Coca cola]]></name>
        <description><![CDATA[Coca-Cola is a carbonated soft drink]]></description>
    </item>
    ...
</shop>
```

{% hint style="info" %}
**Note:**

Even though the shop XML also exposes the `paper` attribute alongside with `flipbookcompleteurl`, we strongly encourage you to use the latter because the former does not include the full public URL of the flipbook.
{% endhint %}

## Loading the exported shop XML

The following is a complete sample of an aspx page that is able to receive and parse the shop XML. What is done with it afterwards is up to you. This sample simply prints the contents of the basket to the page.

```cshtml
<%@ Page Language="C#" AutoEventWireup="true" ValidateRequest="false" %>
<%@ Import Namespace="System.Xml" %>
<%@ Import Namespace="System.Collections.Generic" %>
<%@ Import Namespace="System.Globalization" %>

<script runat="server">
    struct Item
    {
        public string ID;
        public int Amount;
        public decimal Price;
        public string Description;
        public string Name;
    }

    void Page_Load(object sender, EventArgs e)
    {
        if (!String.IsNullOrEmpty(Request.Form["basket"])) {

            var xd = new XmlDocument();
            xd.LoadXml(Request.Form["basket"]);

            var items = new List<Item>();
            foreach (XmlNode xn in xd.SelectNodes("//item"))
            {
                var item = new Item();
                item.Amount = Convert.ToInt32(xn.SelectSingleNode("amount").InnerText);
                item.ID = xn.SelectSingleNode("productid").InnerText;

                string price = xn.SelectSingleNode("price").InnerText;
                if (!String.IsNullOrEmpty(price))
                    item.Price = Convert.ToDecimal(price, new CultureInfo("en-US"));

                item.Description = xn.SelectSingleNode("description").InnerText;
                item.Name = xn.SelectSingleNode("name").InnerText;

                items.Add(item);
            }

            foreach (var item in items)
                Response.Write(String.Format(@"ID: {0}
                                                - Amount: {1}
                                                - Price: {2}
                                                - Description: {3}
                                                - Name: {4}", item.ID, item.Amount, item.Price, item.Description, item.Name));
        }
    }
</script>
```


# Getting Started

The Flipbook JavaScript API allows you to interact with flipbooks, executing commands and subscribing to events.

The Flipbook JavaScript API makes it easy to execute commands in flipbooks as well as listen/subscribe to events happening inside these. The JS API is available both for embedded flipbooks as well as directly in the flipbook viewer through custom scripting.&#x20;

Below are some guides to get you set up and ready to start exploring the possibilities of the JS API.

## Setting up the API for embedded Flipbooks

In order to use the JS API you must include the API script on your page. The API script can be seen below:

```html
<script src='https://cdn.ipaper.io/flipbooks/api/v3/latest.js'></script>
```

Once you have included this script on your page, you will need to instantiate a new instance of the API. When doing so you specify an iframe to target (your embedded flipbook) as well as an API version to use (version 3 is the latest and only version currently available):

```html
<script>
    const iframe = document.getElementById('myFlipbook');
    const instance = iPaperJsApi(iframe, 3);
</script>
```

Once this has been done, you're ready to start using the JS API. You can explore the [available commands and events here](/flipbook-javascript-api/commands-and-events). You will be able to call/use these on the instance you set up above (i.e. `instance.[Category Name].[Command/Event name]`).

When using the JS API with embedded flipbooks, it is possible to have multiple flipbooks where each of them has their own API instance set up. [Please refer to this page on an example of this usage.](/flipbook-javascript-api/advanced-usage#multiple-embedded-flipbooks)

## Setting up the API in Custom Scripting

Using the JS API through the Flipbook Custom Scripting module is simple. The API script is already included in the flipbook viewer, so all you need to do is add a script that instantiates a new instance of the API. When doing so you will need to specify a version to use (version 3 is the latest and only version currently available):

<pre class="language-html"><code class="lang-html">&#x3C;script>
<strong>    const instance = iPaperJsApi(3);
</strong>&#x3C;/script>
</code></pre>

Once this has been done, you're ready to start using the JS API. You can explore the [available commands and events here](/flipbook-javascript-api/commands-and-events). You will be able to call/use these on the instance you set up above (i.e. `instance.[Category Name].[Command/Event name]`).


# Commands & Events

## An introduction to commands & events

A **command** is an action/instruction sent to the Flipbook. These are used to trigger functionality inside the flipbook.

An **event** is when certain things happen inside a flipbook. These can be subscribed to in order to execute code when they occur. The code that should be executed is passed as a callback function when subscribing. You can set up multiple subscriptions to the same event. In addition, when you set up a subscription you will get an unsubscribe function returned which can be used to delete/cancel the subscription at any time (Note: You will need to `await` the subscription setup for the unsubscribe function to be accessible). For example:

```javascript
var mySubscription = await instance.basket.onToggled(() => {
    console.log('The basket was toggled');
});

mySubscription.unsubscribe();
```

The JS API comes with an extensive list of commands and events, which are split into the categories below:

{% content-ref url="/pages/4zE0Bs9b64WIp3wRasfu" %}
[Basket](/flipbook-javascript-api/commands-and-events/basket)
{% endcontent-ref %}

{% content-ref url="/pages/HQ3f7xa57O97a4EUGI3k" %}
[Consent](/flipbook-javascript-api/commands-and-events/consent)
{% endcontent-ref %}

{% content-ref url="/pages/FvI162KpbT856lKdBDRe" %}
[Paging](/flipbook-javascript-api/commands-and-events/paging)
{% endcontent-ref %}

{% content-ref url="/pages/JXFRW0OQLufshIsfpXHa" %}
[Publication](/flipbook-javascript-api/commands-and-events/publication)
{% endcontent-ref %}

{% content-ref url="/pages/YicvZpkPJINRnufWpi9L" %}
[Search](/flipbook-javascript-api/commands-and-events/search)
{% endcontent-ref %}

{% content-ref url="/pages/WXIY9tEiuvM56HP90GwH" %}
[Table of Contents](/flipbook-javascript-api/commands-and-events/table-of-contents)
{% endcontent-ref %}

{% content-ref url="/pages/c8z2n26gMe8MtEbVAQgP" %}
[Sharing](/flipbook-javascript-api/commands-and-events/sharing)
{% endcontent-ref %}

In addition to these commands and events, there is also a `updateConfig` command that can be used to update/set certain config settings of the API instance. The command accepts a single argument, consisting of the properties below:

| Property        | Type   | Description                                                                  |
| --------------- | ------ | ---------------------------------------------------------------------------- |
| `preventAction` | Object | Used to prevent default Flipbook behaviour when certain events are triggered |

For the `preventAction` object inside, this accepts one or multiple of the following properties:

| Property      | Type      | Description                                                                         |
| ------------- | --------- | ----------------------------------------------------------------------------------- |
| `basketClick` | `boolean` | When set to true, this will prevent the native basket from opening                  |
| `itemAdd`     | `boolean` | When set to true, this will prevent all shop items being added to the native basket |

Below is an example use which will prevent the native basket from opening:

```javascript
instance.updateConfig({
    preventAction: {
        basketClick: true,
        itemAdd: true
    }
});
```


# Basket

## Commands

### `open`

Opens the basket.

<pre class="language-javascript"><code class="lang-javascript"><strong>instance.basket.open();
</strong></code></pre>

### `close`

Closes the basket.

```javascript
instance.basket.close();
```

### `getState`

Gets the current state of the basket.

```javascript
// You can either await the result of the command
const result = await instance.basket.getState();

// OR pass a callback
instance.basket.getState((result) => {
    // YOUR CODE HERE
});
```

The command will return an object with the following properties:

| Property | Type      | Description                  |
| -------- | --------- | ---------------------------- |
| `isOpen` | `boolean` | Is the basket currently open |

### `addProduct`

Adds a product to the basket. The command accepts a single argument, consisting of the properties below:

| Property      | Type     | Description                          |
| ------------- | -------- | ------------------------------------ |
| `title`       | `string` | Product title                        |
| `description` | `string` | Product description                  |
| `productId`   | `string` | Product Id                           |
| `price`       | `number` | Product price                        |
| `originPage`  | `number` | Page where the product is located on |
| `quantity`    | `number` | Quanity of product to add            |

Below is an example use:

```javascript
instance.basket.addProduct({
    title: 'My product',
    description: 'Product description',
    productId: 'PROD-25B',
    price: '29.95',
    originPage: 6,
    quantity: 1
});
```

## Events

### `onOpened`

This subscription will trigger every time the basket is opened.

<pre class="language-javascript"><code class="lang-javascript"><strong>instance.basket.onOpened(() => {
</strong>    // YOUR CODE HERE
});
</code></pre>

### `onClosed`

This subscription will trigger every time the basket is closed.

<pre class="language-javascript"><code class="lang-javascript"><strong>instance.basket.onClosed(() => {
</strong>    // YOUR CODE HERE
});
</code></pre>

### `onToggled`

This subscription will trigger every time the basket is toggled between open and closed state.

<pre class="language-javascript"><code class="lang-javascript"><strong>instance.basket.onToggled(() => {
</strong>    // YOUR CODE HERE
});
</code></pre>

### `onProductAdd`

This subscription will trigger whenever a product is added to the basket.

{% hint style="warning" %}
**Note:**

Products added through the `addProduct` API command will not trigger this subscription.
{% endhint %}

```javascript
instance.basket.onProductAdd((product) => {
    // YOUR CODE HERE
});
```

The callback contains a value/result of the product that was added, containing the following properties:

| Property      | Type     | Description                                          |
| ------------- | -------- | ---------------------------------------------------- |
| `title`       | `string` | Product title                                        |
| `description` | `string` | Product description                                  |
| `productId`   | `string` | Product ID                                           |
| `price`       | `number` | Product price                                        |
| `pageIndex`   | `number` | Page index where the product is located\* (see note) |
| `originPage`  | `number` | Page where the product is located on                 |
| `quantity`    | `number` | Quanity of product added                             |
| `packageSize` | `number` | Number of items per package                          |

{% hint style="info" %}
**Note:**

Please note that `pageIndex` starts at `0`. So the 1st page of your flipbook will have a `pageIndex` of `0`, page 2 a `pageIndex` of `1` and so on. To get the actual pagenumber you should use `originPage` instead.
{% endhint %}


# Consent

Through the JS API, you can manage the cookie consent set for the flipbook. This is particularly useful if you want to implement your own cookie solution or a 3rd party one.

{% hint style="info" %}
**Note:**

Prior to implementing custom cookie solutions, please make sure that your Flipbook cookie policy is set to ‘Integration’.
{% endhint %}

{% hint style="info" %}

#### Note:

When using a 3rd party cookie solution, please make sure that the scripts for the solution are fully loaded before you reference it/use it inside any of the consent API methods. In order to avoid issues, we also recommend that you do not load the 3rd party script asynchronously (i.e. using the async keyword in the tag).
{% endhint %}

## Commands

### `get`

Retrieves the active/current cookie consent of a Flipbook.

```javascript
// You can either await the result of the command
const result = await instance.consent.get();

// OR pass a callback
instance.consent.get((result) => {
    // YOUR CODE HERE
});
```

The command will return an object with the following properties:

<table><thead><tr><th width="292.3333333333333">Property</th><th width="144">Type</th><th>Description</th></tr></thead><tbody><tr><td><code>allowPerformanceCookies</code></td><td><code>boolean</code></td><td>Are performance cookies enabled/allowed</td></tr><tr><td><code>allowFunctionalCookies</code></td><td><code>boolean</code></td><td>Are functional cookies enabled/allowed</td></tr><tr><td><code>allowAdvertisingCookies</code></td><td><code>boolean</code></td><td>Are advertising cookies enabled/allowed</td></tr><tr><td><code>allowStrictlyNecessaryCookies</code></td><td><code>boolean</code></td><td>Are strictly necessary cookies enabled/allowed (always <code>true</code>)</td></tr><tr><td><code>hasPreviousCookieConsent</code></td><td><code>boolean</code></td><td>Has user previously given explicit consent</td></tr></tbody></table>

### `set`

This command allows you to set/update the cookie consent for a flipbook. The command requires one argument, consisting of the properties below:

<table><thead><tr><th width="291.3333333333333">Property</th><th width="140">Type</th><th>Description</th></tr></thead><tbody><tr><td><code>allowPerformanceCookies</code></td><td><code>boolean</code></td><td>Should performance cookies be allowed</td></tr><tr><td><code>allowFunctionalCookies</code></td><td><code>boolean</code></td><td>Should functional cookies be allowed</td></tr><tr><td><code>allowAdvertisingCookies</code></td><td><code>boolean</code></td><td>Should advertising cookies be allowed</td></tr></tbody></table>

```javascript
instance.consent.set({
    allowPerformanceCookies: true, 
    allowFunctionalCookies: true, 
    allowAdvertisingCookies: true
});
```

Optionally, you can add a callback to this command which will run once the consent has been sucesfully updated:

```javascript
function consentUpdated(result){
    // YOUR CODE HERE
};

instance.consent.set({
    allowPerformanceCookies: true, 
    allowFunctionalCookies: true, 
    allowAdvertisingCookies: true
}, consentUpdated);
```

The callback contains a value/result of what the cookie consent was sucessfully updated to:

<table><thead><tr><th width="290.3333333333333">Property</th><th width="158">Type</th><th>Description</th></tr></thead><tbody><tr><td><code>allowPerformanceCookies</code></td><td><code>boolean</code></td><td>Are performance cookies enabled/allowed</td></tr><tr><td><code>allowFunctionalCookies</code></td><td><code>boolean</code></td><td>Are functional cookies enabled/allowed</td></tr><tr><td><code>allowAdvertisingCookies</code></td><td><code>boolean</code></td><td>Are advertising cookies enabled/allowed</td></tr><tr><td><code>allowStrictlyNecessaryCookies</code></td><td><code>boolean</code></td><td>Are strictly necessary cookies enabled/allowed (always <code>true</code>)</td></tr><tr><td><code>hasPreviousCookieConsent</code></td><td><code>boolean</code></td><td>Has user previously given explicit consent</td></tr></tbody></table>

### `show`

If you are using iPaper's built-in basic cookie banner, you can toggle the banner to be shown via this command.

```javascript
instance.consent.show();
```

## Events

### `onChange`

This subscription will trigger every time the cookie consent is changed/updated.

```javascript
instance.consent.onChange((result) => {
    // YOUR CODE HERE
});
```

The callback contains a value/result with the following properties:

<table><thead><tr><th width="292.3333333333333">Property</th><th width="151">Type</th><th>Description</th></tr></thead><tbody><tr><td><code>allowPerformanceCookies</code></td><td><code>boolean</code></td><td>Are performance cookies enabled/allowed</td></tr><tr><td><code>allowFunctionalCookies</code></td><td><code>boolean</code></td><td>Are functional cookies enabled/allowed</td></tr><tr><td><code>allowAdvertisingCookies</code></td><td><code>boolean</code></td><td>Are advertising cookies enabled/allowed</td></tr><tr><td><code>allowStrictlyNecessaryCookies</code></td><td><code>boolean</code></td><td>Are strictly necessary cookies enabled/allowed (always <code>true</code>)</td></tr><tr><td><code>hasPreviousCookieConsent</code></td><td><code>boolean</code></td><td>Has user previously given explicit consent</td></tr></tbody></table>

### `onShow`

By listening to this event you can define a callback function that runs when either the `show` API command has been run, or when the “Cookie Policy” button/link in the Flipbook viewer header is clicked. This could for example be used to trigger showing a 3rd party cookie banner.

```javascript
instance.consent.onShow(() => {
    // YOUR CODE HERE
});
```


# Example Implementations

To get you started with implementing your own cookie consent solution for your Flipbooks, we have created a couple of example implementations for some of the biggest 3rd party cookie consent solutions.

{% content-ref url="/pages/NoRLoomb9R6qzMLZ1PTY" %}
[UserCentrics](/flipbook-javascript-api/commands-and-events/consent/example-implementations/usercentrics)
{% endcontent-ref %}

{% content-ref url="/pages/A0ceenNQ8Q9WUzXBpfPn" %}
[OneTrust](/flipbook-javascript-api/commands-and-events/consent/example-implementations/onetrust)
{% endcontent-ref %}

{% content-ref url="/pages/n9jxCydlHVyBfrLyTOeO" %}
[CookieInformation](/flipbook-javascript-api/commands-and-events/consent/example-implementations/cookieinformation)
{% endcontent-ref %}

{% content-ref url="/pages/D1wKLBDmlhZFawiKzeJO" %}
[Cookiebot](/flipbook-javascript-api/commands-and-events/consent/example-implementations/cookiebot)
{% endcontent-ref %}


# UserCentrics

{% hint style="warning" %}
**Note:**

While the implementations below work as intended during the time of writing, we unfortunately cannot guarantee that they will always work for you as-is. Since this is not an official integration with UserCentrics, changes they make to their code base may also have an impact on the snippets below.
{% endhint %}

{% hint style="info" %}
**Note:**

The snippets below are all intended to be used through the Custom Scripting module. If you wish to use them with the Flipbook JavaScript API instead they will require some adjustment.

**Before inserting new `<script>` tags in Custom Scripting containing these snippets, you should ensure that you have loaded the script(s) for UserCentrics first (be aware that loading these&#x20;*****async*****&#x20;can cause issues).**
{% endhint %}

## Setting and updating iPaper Consent

This snippet serves as an example of how you can retrieve the consent given to iPaper in UserCentrics and use it to update the iPaper consent.

First off, you will need to have added iPaper as a service in UserCentrics in the relevant categories. These will likely be Performance, Functionality, and Advertising (dependent on your specific set-up, these categories may be named differently). In order to make the services easier to find once we need to do that in our script, we recommend that you use naming that includes the word `iPaper`.

Once things are set up inside UserCentrics, we can start creating the script.&#x20;

First, we need to instantiate a new iPaper API instance (if this has not been done already) which we can then use to access the Consent Management methods/functions:

```javascript
const instance = iPaperJsApi(3);
```

Next up, we need to introduce a function that will retrieve the consent for iPaper from UserCentrics, and then update the iPaper consent so it matches:

<pre class="language-javascript"><code class="lang-javascript">function setIpaperConsent() {
    // First, let's get all services in UserCentrics where the name includes "iPaper"
    const allServices = UC_UI.getServicesBaseInfo();
    const iPaperServices = allServices.filter(service => service.name.includes('iPaper'));
<strong>    
</strong>    /* 
    NOTE: 
    Dependent on your UserCentrics set-up, some of the services may be in
    custom categories. The best way to check what you will need to filter for
    to find the correct service, is to console.log out iPaperServices and look
    at the categorySlug property
    
    In this example implementation, we will assume that the "Performance" 
    category is custom and use a variable for storing its' categorySlug
    */
    const customPerformanceCategorySlug = {{ INSERT VALUE HERE }}; // Typically looks like 'customCategory-d0fe7968-5445-46a0-bc8d-c197dfa06c1c'
    
    /* 
    Let's filter iPaperServices by categorySlug in order to find the cookie 
    category we're interested in checking the consent status of. Once we have 
    done that, we can access the consent status with .consent.status 
    */
    const allowFunctionalCookies = iPaperServices.filter(service => service.categorySlug === 'essential')[0].consent.status;
    const allowPerformanceCookies = iPaperServices.filter(service => service.categorySlug === customPerformanceCategorySlug)[0].consent.status;
    const allowAdvertisingCookies = iPaperServices.filter(service => service.categorySlug === 'marketing')[0].consent.status;
    
    // Finally, we use the iPaper API to update the iPaper consent
    instance.consent.set({
        allowPerformanceCookies,
        allowFunctionalCookies,
        allowAdvertisingCookies
    });
}
</code></pre>

After this we need to ensure that the `setIpaperConsent()` function will be run in two cases:

1. When the page initializes/loads
2. When the user changes their consent via the UserCentrics banner

```javascript
// Set/update iPaper consent when page initializes/loads
window.addEventListener('UC_UI_INITIALIZED', function () {
    setIpaperConsent();
});

// Listen for changes to UserCentrics consent and update iPaper consent accordingly
window.addEventListener('UC_UI_CMP_EVENT', function (event) {
    if (event.detail.type === 'ACCEPT_ALL' || event.detail.type === 'DENY_ALL' || event.detail.type === 'SAVE') {
        setIpaperConsent();
    }
});
```

And that's all we need to do. Now, with some minor changes to the script to fit your exact UserCentrics set-up, the iPaper consent will now be set and updated so it matches what your users set in the UserCentrics banner.

The full code snippet can be seen below:

```javascript
function setIpaperConsent() {
    // First, let's get all services in UserCentrics where the name includes "iPaper"
    const allServices = UC_UI.getServicesBaseInfo();
    const iPaperServices = allServices.filter(service => service.name.includes('iPaper'));
    
    /* 
    NOTE: 
    Dependent on your UserCentrics set-up, some of the services may be in
    custom categories. The best way to check what you will need to filter for
    to find the correct service, is to console.log out iPaperServices and look
    at the categorySlug property
    
    In this example implementation, we will assume that the "Performance" 
    category is custom and use a variable for storing its' categorySlug
    */
    const customPerformanceCategorySlug = {{ INSERT VALUE HERE }}; // Typically looks like 'customCategory-d0fe7968-5445-46a0-bc8d-c197dfa06c1c'
    
    /* 
    Let's filter iPaperServices by categorySlug in order to find the cookie 
    category we're interested in checking the consent status of. Once we have 
    done that, we can access the consent status with .consent.status 
    */
    const allowFunctionalCookies = iPaperServices.filter(service => service.categorySlug === 'essential')[0].consent.status;
    const allowPerformanceCookies = iPaperServices.filter(service => service.categorySlug === customPerformanceCategorySlug)[0].consent.status;
    const allowAdvertisingCookies = iPaperServices.filter(service => service.categorySlug === 'marketing')[0].consent.status;
    
    // Finally, we use the iPaper API to update the iPaper consent
    instance.consent.set({
        allowPerformanceCookies,
        allowFunctionalCookies,
        allowAdvertisingCookies
    });
}

// Set/update iPaper consent when page initializes/loads
window.addEventListener('UC_UI_INITIALIZED', function () {
    setIpaperConsent();
});

// Listen for changes to UserCentrics consent and update iPaper consent accordingly
window.addEventListener('UC_UI_CMP_EVENT', function (event) {
    if (event.detail.type === 'ACCEPT_ALL' || event.detail.type === 'DENY_ALL' || event.detail.type === 'SAVE') {
        setIpaperConsent();
    }
});
```

## Showing the cookie banner

First, we need to instantiate a new iPaper API instance (if this has not been done already) which we can then use to access the Consent Management methods/functions:

```javascript
const instance = iPaperJsApi(3);
```

In order to show the UserCentrics banner when the "Cookie Policy" link in the topbar menu has been clicked, you can set up an `onShow` subscription like in the snippet below:

```javascript
instance.consent.onShow(() => {
    UC_UI.showFirstLayer();
});
```

If you want to show the detailed UserCentrics sidebar banner instead, you can use this snippet:

```javascript
instance.consent.onShow(() => {
    UC_UI.showSecondLayer();
});
```


# OneTrust

{% hint style="warning" %}
**Note:**

While the implementations below work as intended during the time of writing, we unfortunately cannot guarantee that they will always work for you as-is. Since this is not an official integration with OneTrust, changes they make to their code base may also have an impact on the snippets below.
{% endhint %}

{% hint style="info" %}
**Note:**

The snippets below are all intended to be used through the Custom Scripting module. If you wish to use them with the Flipbook JavaScript API instead they will require some adjustment.

**Before inserting new `<script>` tags in Custom Scripting containing these snippets, you should ensure that you have loaded the script(s) for OneTrust first (be aware that loading these&#x20;*****async*****&#x20;can cause issues).**
{% endhint %}

## Setting and updating iPaper Consent

This snippet is an example of how you can set the iPaper consent to be equal to that of OneTrust.

First, we need to instantiate a new iPaper API instance (if this has not been done already) which we can then use to access the Consent Management methods/functions:

```javascript
const instance = iPaperJsApi(3);
```

Next up, we need to introduce a function that will handle setting the iPaper consent so it matches what the user has set in OneTrust:

<pre class="language-javascript"><code class="lang-javascript">function setIpaperConsent(activeConsentGroups) {
<strong>    /* Get consent for each category:
</strong>    These are retrieved using their OneTrust CustomGroupId.
    By running/using OnetrustActiveGroups in the console, you can see which
    categories are currently accepted from the cookie banner and thereby find
    out exactly what these CustomGroupIds are */
    const performanceCustomGroupId = {{ INSERT VALUE HERE }}; // Typically looks like 'C0001'
    const functionalCustomGroupId = {{ INSERT VALUE HERE }}; // Typically looks like 'C0001'
    const advertisingCustomGroupId = {{ INSERT VALUE HERE }}; // Typically looks like 'C0001'
    
    // Once you know the CustomGroupIds, check each of them to see if they are active
<strong>    const allowPerformanceCookies = activeConsentGroups.includes(performanceCustomGroupId) ? true : false;
</strong>    const allowFunctionalCookies = activeConsentGroups.includes(functionalCustomGroupId) ? true : false;
    const allowAdvertisingCookies = activeConsentGroups.includes(advertisingCustomGroupId) ? true : false;

    // Set iPaper consent to match the above
    instance.consent.set({
      allowPerformanceCookies,
      allowFunctionalCookies,
      allowAdvertisingCookies,
    });
}
</code></pre>

After this we need to ensure that the `setIpaperConsent()` function will be run in two cases:

1. When the page initializes/loads
2. When the user changes their consent via the OneTrust banner

In both of the cases above, we need to pass the param `activeConsentGroups` to `setIpaperConsent()`:

```javascript
let hasInitialized = false;
function OptanonWrapper() {
    if (!hasInitialized) {
        // Set initial iPaper consent to match current OneTrust consent
        const activeConsentGroups = OnetrustActiveGroups?.split(",").filter((element) => element);
        setIpaperConsent(activeConsentGroups);
        
        // Set up callback that will update iPaper consent when OneTrust consent is modified/changed
        OneTrust.OnConsentChanged(function (event) {
            setIpaperConsent(event?.detail);
        });
        
        hasInitialized = true;
    }
}
```

And that's all we need to do. Now, with some minor changes to the script to fit your exact OneTrust set-up, the iPaper consent will now be set and updated so it matches what your users set in the OneTrust banner.

The full code snippet can be seen below:

```javascript
function setIpaperConsent(activeConsentGroups) {
    /* Get consent for each category:
    These are retrieved using their OneTrust CustomGroupId.
    By running/using OnetrustActiveGroups in the console, you can see which
    categories are currently accepted from the cookie banner and thereby find
    out exactly what these CustomGroupIds are */
    const performanceCustomGroupId = {{ INSERT VALUE HERE }}; // Typically looks like 'C0001'
    const functionalCustomGroupId = {{ INSERT VALUE HERE }}; // Typically looks like 'C0001'
    const advertisingCustomGroupId = {{ INSERT VALUE HERE }}; // Typically looks like 'C0001'
    
    // Once you know the CustomGroupIds, check each of them to see if they are active
    const allowPerformanceCookies = activeConsentGroups.includes(performanceCustomGroupId) ? true : false;
    const allowFunctionalCookies = activeConsentGroups.includes(functionalCustomGroupId) ? true : false;
    const allowAdvertisingCookies = activeConsentGroups.includes(advertisingCustomGroupId) ? true : false;

    // Set iPaper consent to match the above
    instance.consent.set({
      allowPerformanceCookies,
      allowFunctionalCookies,
      allowAdvertisingCookies,
    });
}

let hasInitialized = false;
function OptanonWrapper() {
    if (!hasInitialized) {
        // Set initial iPaper consent to match current OneTrust consent
        const activeConsentGroups = OnetrustActiveGroups?.split(",").filter((element) => element);
        setIpaperConsent(activeConsentGroups);
        
        // Set up callback that will update iPaper consent when OneTrust consent is modified/changed
        OneTrust.OnConsentChanged(function (event) {
            setIpaperConsent(event?.detail);
        });
        
        hasInitialized = true;
    }
}
```

## Showing the cookie banner

First, we need to instantiate a new iPaper API instance (if this has not been done already) which we can then use to access the Consent Management methods/functions:

```javascript
const instance = iPaperJsApi(3);
```

In order to show the OneTrust banner when the "Cookie Policy" link in the topbar menu has been clicked, you can set up an `onShow` subscription like in the snippet below:

```javascript
let hasInitialized = false;
function OptanonWrapper() {
    if (!hasInitialized) {
        instance.consent.onShow(() => {
            OneTrust.InitializeBanner();
            OneTrust.LoadBanner();
        });
    }
}
```


# CookieInformation

{% hint style="warning" %}
**Note:**

While the implementations below work as intended during the time of writing, we unfortunately cannot guarantee that they will always work for you as-is. Since this is not an official integration with CookieInformation, changes they make to their code base may also have an impact on the snippets below.
{% endhint %}

{% hint style="info" %}
**Note:**

The snippets below are all intended to be used through the Custom Scripting module. If you wish to use them with the Flipbook JavaScript API instead they will require some adjustment.

**Before inserting new `<script>` tags in Custom Scripting containing these snippets, you should ensure that you have loaded the script(s) for CookieInformation first (be aware that loading these&#x20;*****async*****&#x20;can cause issues).**
{% endhint %}

## Setting and updating iPaper Consent

This snippet is an example of how you can set the iPaper consent to be equal to that of CookieInformation.

First, we need to instantiate a new iPaper API instance (if this has not been done already) which we can then use to access the Consent Management methods/functions:

```javascript
const instance = iPaperJsApi(3);
```

Next up, we need to introduce a function that will retrieve the current consent from CookieInformation, and then update the iPaper consent so it matches:

```javascript
function setIpaperConsent() {
    // Get consent for each category
    /* By default, the below will be the names for the categories. However, 
    dependent on your specific set-up, these can have custom names instead */
    const allowPerformanceCookies = CookieInformation.getConsentGivenFor('cookie_cat_statistic') ? true : false;
    const allowFunctionalCookies = CookieInformation.getConsentGivenFor('cookie_cat_functional') ? true : false;
    const allowAdvertisingCookies = CookieInformation.getConsentGivenFor('cookie_cat_marketing') ? true : false;
    
    // Set iPaper consent to match the above
    instance.consent.set({
        allowPerformanceCookies,
        allowFunctionalCookies,
        allowAdvertisingCookies
    });
}
```

After this we need to ensure that the `setIpaperConsent()` function will be run in two cases:

1. When the page initializes/loads
2. When the user changes their consent via the CookieInformation banner

```javascript
// Set/update iPaper consent when page initializes/loads
if (window.CookieInformationScriptLoaded){
    setIpaperConsent();
}

// Listen for changes to CookieInformation consent and update iPaper consent accordingly
window.addEventListener('CookieInformationConsentGiven', () => setIpaperConsent());
```

And that's it!&#x20;

With the full snippet (see below), the iPaper consent will now be set and updated so it matches what your users set in the CookieInformation banner.

```javascript
function setIpaperConsent() {
    // Get consent for each category
    /* By default, the below will be the names for the categories. However, 
    dependent on your specific set-up, these can have custom names instead */
    const allowPerformanceCookies = CookieInformation.getConsentGivenFor('cookie_cat_statistic') ? true : false;
    const allowFunctionalCookies = CookieInformation.getConsentGivenFor('cookie_cat_functional') ? true : false;
    const allowAdvertisingCookies = CookieInformation.getConsentGivenFor('cookie_cat_marketing') ? true : false;
    
    // Set iPaper consent to match the above
    instance.consent.set({
        allowPerformanceCookies,
        allowFunctionalCookies,
        allowAdvertisingCookies
    });
}

// Set/update iPaper consent when page initializes/loads
if (window.CookieInformationScriptLoaded)
    setIpaperConsent();

// Listen for changes to CookieInformation consent and update iPaper consent accordingly
window.addEventListener('CookieInformationConsentGiven', () => setIpaperConsent());
```

## Showing the cookie banner

First, we need to instantiate a new iPaper API instance (if this has not been done already) which we can then use to access the Consent Management methods/functions:

```javascript
const instance = iPaperJsApi(3);
```

To show the CookieInformation banner when the "Cookie Policy" link in the top bar menu has been clicked, you can set up an `onShow` subscription like in the snippet below:

```javascript
instance.consent.onShow(() => {
    CookieConsent.show();
});
```


# Cookiebot

{% hint style="warning" %}
**Note:**

While the implementations below work as intended during the time of writing, we unfortunately cannot guarantee that they will always work for you as-is. Since this is not an official integration with Cookiebot, changes they make to their code base may also have an impact on the snippets below.
{% endhint %}

{% hint style="info" %}
**Note:**

The snippets below are all intended to be used through the Custom Scripting module. If you wish to use them with the Flipbook JavaScript API instead they will require some adjustment.

**Before inserting new `<script>` tags in Custom Scripting containing these snippets, you should ensure that you have loaded the script(s) for** Cookiebot **first (be aware that loading these&#x20;*****async*****&#x20;can cause issues).**
{% endhint %}

## Setting and updating iPaper Consent&#x20;

This snippet is an example of how you can set the iPaper consent to be equal to that of Cookiebot.

First, we need to instantiate a new iPaper API instance (if this has not been done already) which we can then use to access the Consent Management methods/functions:

```javascript
const instance = iPaperJsApi(3);
```

First, we need to introduce a function that will retrieve the current consent from Cookiebot, and then update the iPaper consent, so it matches:

```javascript
function setIpaperConsent() {
    // Get consent for each category
    /* By default, the below will be the names for the categories. However, 
    dependent on your specific set-up, these can have custom names instead */
    const allowPerformanceCookies = Cookiebot.consent.statistics  ? true : false;
    const allowFunctionalCookies = Cookiebot.consent.preferences  ? true : false;
    const allowAdvertisingCookies = Cookiebot.consent.marketing ? true : false;
    // Set iPaper consent to match the above
    instance.consent.set({
        allowPerformanceCookies,
        allowFunctionalCookies,
        allowAdvertisingCookies
    });
}
```

Next up, we need to ensure that the `setIpaperConsent()` function will be run in two cases:

1. When the page initializes/loads
2. When the user changes their consent via the Cookiebot banner

```javascript
// Listen for changes to Cookiebot consent and update iPaper consent accordingly
window.addEventListener('CookiebotOnConsentReady', () => setIpaperConsent());

iPaper.API.consent.onShow(() => {
    Cookiebot.show();
});
```

And that's it!&#x20;

With the full snippet (see below), the iPaper consent will now be set and updated, so it matches what your users set in the Cookiebot banner.

<pre class="language-html"><code class="lang-html">&#x3C;script id="Cookiebot" src="https://consent.cookiebot.com/uc.js" data-cbid="YOUR UNIQE COOKIEBOT CBID" type="text/javascript" async>&#x3C;/script>
<strong>&#x3C;script>
</strong><strong>// Instantiate new API instance
</strong>const instance = iPaperJsApi(3);

<strong>// Sets the iPaper consent to match the current Cookiebot consent
</strong>function setIpaperConsent() {
    // Get consent for each category
    /* By default, the below will be the names for the categories. However, 
    dependent on your specific set-up, these can have custom names instead */
    const allowPerformanceCookies = Cookiebot.consent.statistics  ? true : false;
    const allowFunctionalCookies = Cookiebot.consent.preferences  ? true : false;
    const allowAdvertisingCookies = Cookiebot.consent.marketing ? true : false;
    
    // Set iPaper consent to match the above
    instance.consent.set({
        allowPerformanceCookies,
        allowFunctionalCookies,
        allowAdvertisingCookies
    });

}

// Listen for changes to Cookiebot consent and update iPaper consent accordingly
window.addEventListener('CookiebotOnConsentReady', () => setIpaperConsent());

instance.consent.onShow(() => {
    Cookiebot.show();
});
&#x3C;/script>
</code></pre>

## Showing the cookie banner

First, we need to instantiate a new iPaper API instance (if this has not been done already) which we can then use to access the Consent Management methods/functions:

```javascript
const instance = iPaperJsApi(3);
```

To show the Cookiebot banner when the "Cookie Policy" link in the top bar menu has been clicked, you can set up an `onShow` subscription like in the snippet below:

```javascript
instance.consent.onShow(() => {
    CookieConsent.show();
});
```


# Paging

## Commands

### `goToPage`

Navigates to the specified page in the flipbook.

```javascript
instance.paging.goToPage(4);
```

### `goToNextPage`

Navigates to the next page in the flipbook. If the flipbook has a spread-based layout, it will navigate to the next spread.

```javascript
instance.paging.goToNextPage();
```

### `goToPrevPage`

Navigates to the previous page in the flipbook. If the flipbook has a spread-based layout, it will navigate to the previous spread.

```javascript
instance.paging.goToPrevPage();
```

### `getState`

Gets the current paging state.

```javascript
// You can either await the result of the command
const result = await instance.paging.getState();

// OR pass a callback
instance.paging.getState((result) => {
    // YOUR CODE HERE
});
```

The command will return an object with the following properties:

| Property        | Type                | Description                               |
| --------------- | ------------------- | ----------------------------------------- |
| `currentSpread` | `number[]`          | The current visible pages of the flipbook |
| `spreads`       | Array of `number[]` | All spreads of the flipbook               |
| `totalPages`    | `number`            | Total amount of pages in the flipbook     |

## Events

### `onChange`

This subscription will trigger when the spread of a flipbook changes.

```javascript
instance.paging.onChange((result) => {
    // YOUR CODE HERE
});
```

The callback contains a value/result with the following properties:

| Property        | Type                | Description                               |
| --------------- | ------------------- | ----------------------------------------- |
| `currentSpread` | `number[]`          | The current visible pages of the flipbook |
| `spreads`       | Array of `number[]` | All spreads of the flipbook               |
| `totalPages`    | `number`            | Total amount of pages in the flipbook     |


# Publication

## Commands

### `getDetails`

Retrieves details of the flipbook.

```javascript
instance.publication.getDetails((result) => {
    // YOUR CODE HERE
});
```

The command will return an object with the following properties:

| Property     | Type      | Description                      |
| ------------ | --------- | -------------------------------- |
| `name`       | `string`  | Name of the flipbook             |
| `seoTitle`   | `string`  | SEO title of the flipbook        |
| `paperId`    | `number`  | ID of the flipbook               |
| `isEmbedded` | `boolean` | Is the flipbook embedded/iframed |

## Events

### `onSpreadChange`

{% hint style="warning" %}
**Note:**

For new implementations, we reccommend you use the [`paging.onChange`](/flipbook-javascript-api/commands-and-events/paging#onchange) event instead of this event.
{% endhint %}

This subscription will trigger when the spread of the flipbook changes.

```javascript
instance.publication.onSpreadChange((result) => {
    // YOUR CODE HERE
});
```

The callback contains a value/result with the following properties:

| Property              | Type       | Description                                                                                                                                                                                                        |
| --------------------- | ---------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `currentSpreadPages`  | `number[]` | Page numbers that are in the current spread.                                                                                                                                                                       |
| `currentVisiblePages` | `number[]` | Page number(s) that are currently in view. This array may contain one or two numbers, depending if the current view is page- (mobile/tablet in portrait) or spread-based (desktop, or mobile/tablet in landscape). |

### `onPageElementClick`

This subscription will trigger when a product/shop item action is clicked.

```javascript
instance.publication.onPageElementClick((result) => {
    // YOUR CODE HERE
});
```

{% hint style="info" %}
**Note:**

Current only shop items will fire this event. Click events on any other enrichments are not emitted.
{% endhint %}

The callback contains a value/result with the following properties:

<table><thead><tr><th width="283.3333333333333">Property</th><th width="175">Type</th><th>Description</th></tr></thead><tbody><tr><td><code>type</code></td><td><code>string</code></td><td>The page element type that was clicked on</td></tr><tr><td><code>data</code></td><td><code>{}</code></td><td>Data associated with the page element</td></tr><tr><td><code>data.title</code></td><td><code>string</code></td><td>Shop item title</td></tr><tr><td><code>data.description</code></td><td><code>string</code></td><td>Shop item description</td></tr><tr><td><code>data.productId</code></td><td><code>string</code></td><td>Shop item product ID</td></tr><tr><td><code>data.price</code></td><td><code>number</code></td><td>Shop item price</td></tr><tr><td><code>data.amountSelection</code></td><td><code>boolean</code></td><td>If users are allowed to manually specify an amount before adding to basket</td></tr><tr><td><code>data.packageSize</code></td><td><code>number</code></td><td>Number of items per package</td></tr><tr><td><code>event</code></td><td><code>{}</code></td><td>Event data associated with the click event</td></tr><tr><td><code>event.originPage</code></td><td><code>number</code></td><td>Page number on which the page element is located on</td></tr><tr><td><code>event.originSpreadPages</code></td><td><code>number[]</code></td><td>Page numbers on which the spread where the page element is located on</td></tr><tr><td><code>event.coordinates.pageX</code></td><td><code>number</code></td><td>Absolute x coordinate of the click event</td></tr><tr><td><code>event.coordinates.pageY</code></td><td><code>number</code></td><td>Absolute y coordinate of the click event</td></tr><tr><td><code>event.coordinates.bookX</code></td><td><code>number</code></td><td>Ratio of the x coordinate relative to book width</td></tr><tr><td><code>event.coordinates.bookY</code></td><td><code>number</code></td><td>Ratio of the y coordinate relative to book height</td></tr></tbody></table>

### `onDownload`

This subscription will trigger when the download button in the topbar is clicked (if enabled in Flipbook settings).

```
instance.publication.onDownload(() => {
    // YOUR CODE HERE
});
```


# Search

## Commands

### `open`

Opens the search aside.

```javascript
instance.search.open();
```

### `close`

Closes the search aside.

```javascript
instance.search.close();
```

### `query`

Opens the search aside and executes a search for the passed query text.

```javascript
instance.search.query({ text: 'YOUR SEARCH QUERY' });
```

## Events

### `onSearch`

This subscription will trigger when a search is performed in the Flipbook.

```
instance.search.onSearch((result) => {
    // YOUR CODE HERE
});
```

The callback contains a value/result with the following properties:

| Property | Type     | Description                                              |
| -------- | -------- | -------------------------------------------------------- |
| query    | `string` | Search query that was used                               |
| results  | `number` | Amount of search results found in Flipbook for the query |


# Table of Contents

## Commands

### `open`

Opens the table of contents.

```javascript
instance.tableOfContents.open();
```

### `close`

Closes the table of contents.

```javascript
instance.tableOfContents.close();
```

### `getState`

Gets the current table of contents state.

```javascript
// You can either await the result of the command
const result = await instance.tableOfContents.getState();

// OR pass a callback
instance.tableOfContents.getState((result) => {
    // YOUR CODE HERE
});
```

The command will return an object with the following properties:

| Property | Type      | Description                             |
| -------- | --------- | --------------------------------------- |
| `isOpen` | `boolean` | Is the table of contents currently open |

## Events

### `onOpen`

This subscription will trigger when the table of contents is opened.

```
instance.tableOfContents.onOpen(() => {
    // YOUR CODE HERE
});
```


# Sharing

## Events

### `onShare`

This subscription will trigger whenever a user clicks the share icon.

{% hint style="warning" %}
**Note:**

Once you attach your listener to the "onShare" event, iPaper's native share functionality (copying the URL to clipboard on click) will be disabled. You will have to handle sharing using your own logic.

You can revert back to using iPaper's native sharing by simply [unsubscribing ](/flipbook-javascript-api/commands-and-events#an-introduction-to-commands-and-events)from the "onShare" event.
{% endhint %}

```javascript
instance.share.onShare((sharePayload) => {
    // YOUR CODE HERE
});
```

The share payload contains all properties that can be used to link back to the Flipbook and open it on the page where the share event was invoked:

| Property  | Type     | Description                                                        |
| --------- | -------- | ------------------------------------------------------------------ |
| `baseUrl` | `string` | The base URL of the shared Flipbook                                |
| `url`     | `string` | The full URL of the shared Flipbook which includes the page number |
| `page`    | `number` | The number of the page where the share event was triggered         |


# Advanced Usage

## Multiple embedded flipbooks

If you have a page containing multiple embedded flipbooks, you can set up an API instance on each of them where you can execute commands and set up subscriptions. Below is an example of a page containing multiple flipbooks each with their own API instance.

```html
<!DOCTYPE html>
  <html>
    <head>
        <title>My page</title>
    </head>
    <body>
        <!-- Embedded Flipbooks -->
        <iframe src="https://demo.ipaper.io" id="myFlipbook"></iframe>
        <iframe src="https://demo.ipaper.io" id="myOtherFlipbook"></iframe>
        
        <!-- JS API embed script -->
        <script src='https://cdn.ipaper.io/flipbooks/api/v3/latest.js'></script>
        
        <!-- Custom script -->
        <script>
            const myFlipbook = document.getElementById('myFlipbook');
            const myFlipbookInstance = iPaperJsApi(myFlipbook, 3);
            myFlipbookInstance.publication.onSpreadChange((result) => {
                console.log('Spread changed in myFlipbook', result);
            });
            
            const myOtherFlipbook= document.getElementById('myOtherFlipbook');
            const myOtherFlipbookInstance= iPaperJsApi(myOtherFlipbook, 3);
            myOtherFlipbookInstance.publication.onSpreadChange((result) => {
                console.log('Spread changed in myOtherFlipbook', result);
            });
        </script>
    </body>
</html>
```


# Migration guide

This page will help you through the process of migrating an existing V2 implementation to the new V3 Flipbook JavaScript API. The new V3 API contains all the features from V2 as well as new ones.

## Why you should consider migrating

### New commands & events

The V3 JS API comes with new functionality that could be useful to you. In addition, more functionality will be added over time, which won't be the case with V2.

Some of the new functionality in V3 include:

* The ability to check the current state of paging, basket, search, and table of contents
* The ability to explicitly close or open basket, search, and table of contents
* The ability to retrieve the details of the flipbook via [publication.getDetails](/flipbook-javascript-api/commands-and-events/publication#getdetails)
* The ability to execute a search directly via [search.query](/flipbook-javascript-api/commands-and-events/search#query)

### Improved stability and performance

The way the V3 JS API has been built improves both stability and general performance of commands and events. Another thing that has been particularly streamlined and improved is the initialization process.

### Better integration with multiple flipbooks

Due to the improvements made to the initialization process, it is now much easier to work with pages containing multiple embedded flipbooks. For more on this, [see this page](/flipbook-javascript-api/advanced-usage#multiple-embedded-flipbooks).

### The API can now be used via Custom Scripting

Unlike the V2 JS API, the V3 JS API can be used directly inside Custom Scripting with feature parity to when used externally. For how to get started with this, [see this page](/flipbook-javascript-api/getting-started#setting-up-the-api-in-custom-scripting).

## Step 1: Migrate API initialization

#### Updating the API embed script

To migrate to the new V3 API, you will need to update the API embed script on your page. Your current embed script should look something like this:

{% code title="Example of V2 API embed script" %}

```html
<script>
(function(i,P,a,p,e,r){if(i.getElementById(a=a+'-'+e))return;
r=i.querySelector(P).parentNode.appendChild(i.createElement(P));
r.id=a;r.async=1;r.src=p+'/'+e+'.js'})
(document,'script','ipaper-api','<ApiBaseUrl>','<ApiSecret>');
</script>
```

{% endcode %}

The new embed script for V3 can be seen below:

{% code title="New V3 API embed script" %}

```html
<script src='https://cdn.ipaper.io/flipbooks/api/v3/latest.js'></script>
```

{% endcode %}

Once you have the added the new embed script, simply remove/delete the old one from the page.

#### Updating existing script to initialize API instance

The way of initializing the API has changed a bit in V3. Instead of relying on an automatic process of detecting the iframe containing a flipbook and instantiating the API with this iframe, you will now need to instantiate the API manually by passing in the iframe and an API version as parameters.

So, where you previously may have had something like this:

<pre class="language-html" data-title="Example V2 initialization"><code class="lang-html">&#x3C;iframe src="https://demo.ipaper.io">&#x3C;/iframe>

&#x3C;script>
<strong>    function iPaperInit() {
</strong><strong>        // Your API commands and events here
</strong><strong>    }
</strong>&#x3C;/script>
</code></pre>

Or something like this:

{% code title="Example V2 initialization via event listener" %}

```markup
<iframe src="https://demo.ipaper.io"></iframe>

<script>
    document.addEventListener('iPaperInit', function() {
        // Your API commands and events here
    });
</script>
```

{% endcode %}

You will now need this instead:

{% code title="Example V3 initialization" %}

```html
<iframe src="https://demo.ipaper.io" id="myFlipbook"></iframe>

<script>
    /* 
    Locate the iframe you want the API to be instantiated on. 
    This could be done multiple different ways, but for this example we will rely
    on the iframe having a unique ID.
    */
    const myIframe = document.getElementById('myFlipbook');
    
    // Create a new V3 API instance with the iframe
    const myApiInstance = iPaperJsApi(myIframe, 3);
</script>
```

{% endcode %}

{% hint style="info" %}
**Note:**

This way of instantiating the API will only work for V3 and later.
{% endhint %}

Once this has been done, you can use/access API commands and events directly on the created `instance` variable (FX. `myApiInstance.basket.open()`).

**Are you using multiple embedded flipbooks on the same page?**

The change to initialization will also make it much easier to work with pages containing multiple embedded Flipbooks. For more on how to create multiple API instances on the same page, [see here.](/flipbook-javascript-api/advanced-usage)

If you are currently using the V2 way of having multiple embedded flipbooks, you will need to update this to be done in the new V3 way, as can be seen in the link above. The V2 way is not supported in V3.

## Step 2: Migrate API commands

API commands generally work the same in V3 as they did in V2. They have, however, been split into more detailed categories, and some have been renamed or altered slightly. Below, you can see the V2 API commands and their V3 equivalents.

{% hint style="danger" %}
**Note:**

[Command triggers](/flipbook-javascript-api/legacy-javascript-api-v2/advanced-usage#event-listeners-and-command-triggers) are no longer supported with V3. Any usage of these will have to be updated.
{% endhint %}

### `updateEventSettings`

This command has been changed a bit in V3. It has been converted to be a more universal command for updating an API instance's configuration.

So, where you may have had something like this before:

```javascript
iPaperAPI.updateEventSettings({
    onBasketClick: { preventDefault: true },
    onItemAdd: { preventDefault: true }
});
```

You will now need something like this instead:

<pre class="language-javascript"><code class="lang-javascript">myApiInstance.updateConfig({
    preventAction: {
        basketClick: true,
        itemAdd: true
<strong>    }
</strong>});
</code></pre>

`basketClick` is equivalent to V2's `onBasketClick` and `itemAdd` is equivalent to `onItemAdd`.

### `goToPage`

The **location** for this command has been changed. It can now be accessed with:

```javascript
myApiInstance.paging.goToPage(4);
```

### `goToPreviousPage`

The **location** and **naming** for this command has been changed. It can now be accessed with:

```javascript
myApiInstance.paging.goToPrevPage();
```

### `goToNextPage`

The **location** for this **command** has been changed. It can now be accessed with:

```javascript
myApiInstance.paging.goToNextPage();
```

### `addItem`

{% hint style="warning" %}
**Be aware:**

The `productID` property has been renamed `productId` in V3.
{% endhint %}

The **location** and **naming** for this command has been changed. It can now be accessed with:

```javascript
myApiInstance.basket.addProduct({
    title: 'My product',
    description: 'Product description',
    productId: 'PROD-25B',
    price: '29.95',
    originPage: 6,
    quantity: 1
});
```

### `getCookieConsent`

The **location** and **naming** for this command has been changed. It can now be accessed with:

```javascript
// You can either await the result of the command
const result = await myApiInstance.consent.get();

// OR pass a callback
myApiInstance.consent.get((result) => {
    // YOUR CODE HERE
});
```

### `updateCookieConsent`

The **location** and **naming** for this command has been changed. It can now be accessed with:

```javascript
myApiInstance.consent.set({
    allowPerformanceCookies: true, 
    allowFunctionalCookies: true, 
    allowAdvertisingCookies: true
});
```

## Step 3: Migrate API events

In V3, events have been streamlined and updated to run as subscriptions. This makes it easier to add and remove these on demand. For more on this, [see this page](/flipbook-javascript-api/commands-and-events).

Apart from this change, API events generally work the same in V3 as they did in V2. They have, however, been split into more detailed categories, and some have been renamed. Below, you can see the V2 API events and their V3 equivalents.

{% hint style="danger" %}
**Note:**

[Event listeners](/flipbook-javascript-api/legacy-javascript-api-v2/advanced-usage#event-listeners-and-command-triggers) are no longer supported with V3. Any usage of these will have to be updated.
{% endhint %}

### `onPageElementClick`

The **location** for this event has been changed. It can now be subscribed to using:

```javascript
myApiInstance.publication.onPageElementClick((result) => {
    // YOUR CALLBACK CODE HERE
});
```

### `onSpreadChange`

The **location** for this event has been changed. It can now be subscribed to using:

```javascript
myApiInstance.publication.onSpreadChange((result) => {
    // YOUR CALLBACK CODE HERE
});
```

However, while this command is available in V3, we recommend that you consider upgrading to/using the new `paging.onChange` event instead.

### `onBasketClick`

The **location** and **naming** for this event has been changed. It can now be subscribed to using:

```javascript
myApiInstance.basket.onToggled(() => {
    // YOUR CALLBACK CODE HERE
});
```

### `onItemAdd`

The **location** and **naming** for this event has been changed. It can now be subscribed to using:

```javascript
myApiInstance.basket.onProductAdd((product) => {
    // YOUR CALLBACK CODE HERE
});
```

### `onCookieConsentShown`

The **location** and **naming** for this event has been changed. It can now be subscribed to using:

```javascript
myApiInstance.consent.onShow(() => {
    // YOUR CALLBACK CODE HERE
});
```

### `onCookieConsentUpdate`

The **location** and **naming** for this event has been changed. It can now be subscribed to using:

```javascript
myApiInstance.consent.onChange((result) => {
    // YOUR CALLBACK CODE HERE
});
```


# Legacy Javascript API (v2)

{% hint style="warning" %}
**Note:**

If you are just getting started with the Flipbook JS API, we strongly advice that you use the latest version (v3) instead.

If you are already using the v2 JS API, we encourage you to upgrade to v3 if possible.
{% endhint %}

Here you will find the legacy documentation for the v2 Flipbook JavaScript API.&#x20;


# Quick Start

The v2 Flipbook JavaScript API allows for streamlined interaction with iframed flipbooks using cross-origin communications available in modern browsers.

## Overview

{% hint style="info" %}
**Note:**&#x20;

The legacy v2 JS API is now superseded by v3 Flipbook JS API.
{% endhint %}

The v2 Flipbook JavaScript API uses the [`window.postMessage` API](https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage) for two-way communications between the parent window and one or more flipbooks embedded in the parent window inside `<iframe>` elements.

For this to work, you will need to include the API script on your page. [Please refer to this help article](https://intercom.help/ipaper/do-more-with-ipaper/the-ipaper-api-javascript) on how to obtain the iPaper Flipbook JavaScript API script from the admin. The generated code will look something as follow:

```html
<!-- Start of async iPaper Flipbook API script -->
<script>
(function(i,P,a,p,e,r){if(i.getElementById(a=a+'-'+e))return;
r=i.querySelector(P).parentNode.appendChild(i.createElement(P));
r.id=a;r.async=1;r.src=p+'/'+e+'.js'})
(document,'script','ipaper-api','<ApiBaseUrl>','<ApiSecret>');
</script>
<!-- End of async iPaper Flipbook API script -->
```

{% hint style="info" %}
**Note:**

Both `<ApiBaseUrl>` and `<ApiSecret>` in the snippet above will be substituted with partner and license-dependent configurations and be automatically populated, when you retrieve the API script from the admin.
{% endhint %}

## Minimal config setup

Most of our customers use our API to interact with a single instance of a flipbook on their page. A frictionless, minimal config setup is therefore implemented—meaning that for use-cases involving single flipbooks iframed on a single page, no additional configuation or markup changes are required. While the setup will automatically attempt to identify the first iframed flipbook on the page, we do still reccommend adding the `data-ipaper` attribute to the iframe to ensure the most reliable detection:

```html
<iframe src="https://demo.ipaper.io" data-ipaper></iframe>
```

{% hint style="info" %}
&#x20;**Note:** If you are using multiple flipbooks, additional configuration is required: please refer to the [**Multiple flipbooks**](#multiple-flipbooks) section for further information.
{% endhint %}

## Interacting with iframed flipbook

The embedded script will call the `iPaperInit()` method that is defined globally in the parent window, and you can then interact with the iframed flipbook via the `iPaperAPI` instance created by default:

```html
<!doctype html>
<html>
<head>
    <title>Example Flipbook JS API use</title>

    <!-- Start of async iPaper Flipbook API script -->
    <!-- NOTE: The entire code snippet below can be obtained directly from the Admin. Refer to our help article for further information. -->
    <script>
    (function(i,P,a,p,e,r){if(i.getElementById(a=a+'-'+e))return;
    r=i.querySelector(P).parentNode.appendChild(i.createElement(P));
    r.id=a;r.async=1;r.src=p+'/'+e+'.js'})
    (document,'script','ipaper-api','<ApiBaseUrl>','<ApiSecret>');
    </script>
    <!-- End of async iPaper Flipbook API script -->
    
    <script>
        function iPaperInit() {
            // Define a callback for the `onSpreadChange` event
            // `iPaperAPI` is the default first instance of the API
            iPaperAPI.onSpreadChange = function(data) {
                console.log(data);
            }

            // Navigate to page 4 when a button is clicked
            document.getElementById('page-4-button').addEventListener('click', function() {
                iPaperAPI.goToPage(4);
            });
        }
    </script>
</head>
<body>
    <iframe src="https://demo.ipaper.io" style="width: 100%; height: 400px;" data-ipaper></iframe>
    <button type="button" id="page-4-button">Go to page 4</button>
</body>
</html>
```

The `iPaperAPI` object exposes several methods that can be grouped into two categories: [**magic event callbacks**](/flipbook-javascript-api/legacy-javascript-api-v2/events) and [**magic commands**](/flipbook-javascript-api/legacy-javascript-api-v2/commands).

* **Events happen on the flipbook and are emitted to the parent window**, which trigger magic callbacks. An example of a flipbook event is the [`onSpreadChange`](/flipbook-javascript-api/legacy-javascript-api-v2/events#onspreadchange) event. [Read more](/flipbook-javascript-api/legacy-javascript-api-v2/events) about how to listen to these events and react to them accordingly.
* **Commands are instructions issued from the parent window to the flipbook**, done via invoking magic commands. An example of a flipbook command is the [`goToPage`](/flipbook-javascript-api/legacy-javascript-api-v2/commands#gotopage) method. [Read more](/flipbook-javascript-api/legacy-javascript-api-v2/commands) about how to issue commands to instruct your iframed flipbook to perform specified actions.

As an alternative to using the `iPaperInit` function like in the snippet above, you can also use an event listener for `iPaperInit` instead:

```html
<!doctype html>
<html>
<head>
    <title>Example Flipbook JS API use</title>

    <!-- Start of async iPaper Flipbook API script -->
    <!-- NOTE: The entire code snippet below can be obtained directly from the Admin. Refer to our help article for further information. -->
    <script>
    (function(i,P,a,p,e,r){if(i.getElementById(a=a+'-'+e))return;
    r=i.querySelector(P).parentNode.appendChild(i.createElement(P));
    r.id=a;r.async=1;r.src=p+'/'+e+'.js'})
    (document,'script','ipaper-api','<ApiBaseUrl>','<ApiSecret>');
    </script>
    <!-- End of async iPaper Flipbook API script -->
    
    <script>
        document.addEventListener('iPaperInit', function() {
            // Define a callback for the `onSpreadChange` event
            // `iPaperAPI` is the default first instance of the API
            iPaperAPI.onSpreadChange = function(data) {
                console.log(data);
            }

            // Navigate to page 4 when a button is clicked
            document.getElementById('page-4-button').addEventListener('click', function() {
                iPaperAPI.goToPage(4);
            });
        });
    </script>
</head>
<body>
    <iframe src="https://demo.ipaper.io" style="width: 100%; height: 400px;" data-ipaper></iframe>
    <button type="button" id="page-4-button">Go to page 4</button>
</body>
</html>
```

## Multiple flipbooks

In the event that multiple flipbooks are to be embedded via `<iframe>` on the same page, some additional configuration is required. Refer to the [**Multiple Flipbooks**](/flipbook-javascript-api/legacy-javascript-api-v2/advanced-usage#multiple-flipbooks) section for further implementation details.


# Commands

Commands are instructions issued by the parent window to the iframed flipbook. Commands can be invoked by calling magic methods, which can be accessed directly from the API instance.

## Overview

![](https://3504352165-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FW0UCgeNLp5UrqOZTjvsN%2Fuploads%2F4aUdn5VnTlBfawOnAq68%2Fcommands.svg?alt=media\&token=229ae843-709c-4e65-9802-fabe1931926e)

Commands are issued from the parent window (your website) to the JavaScript API. The API then relays the command via `window.postMessage()` to the iframed flipbook.

## Flipbook-related commands

### `updateEventSettings`

It is possible to dictate how the iframed flipbook should handle events when they have been triggered. The method accepts a single argument that is an object containing the following keys, which references events that the v2 Flipbook API listens to:

* [`onBasketClick`](/flipbook-javascript-api/legacy-javascript-api-v2/events#onbasketclick)
* [`onItemAdd`](/flipbook-javascript-api/legacy-javascript-api-v2/events#onitemadd)

The keys should contain an object of the following format: `{ preventDefault: <boolean> }`, where `<boolean>` is `true` or `false`. When `preventDefault` is set to `true`, the flipbook will skip the default action associated with each event. If set to `false`, the default action will be performed.

| Event                                                                                     | Default action taking place in the flipbook                                                                             |
| ----------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------- |
| [`onBasketClick`](/flipbook-javascript-api/legacy-javascript-api-v2/events#onbasketclick) | Opens the shop basket                                                                                                   |
| [`onItemAdd`](/flipbook-javascript-api/legacy-javascript-api-v2/events#onitemadd)         | <p>Adds the item to the shop basket.<br>Triggered by any enrichment that adds a shop item (e.g. shop variant menu).</p> |

An example use:

```javascript
// Disable opening of shop basket AND adding shop items
iPaperAPI.updateEventSettings({
	onBasketClick: { preventDefault: true },
	onItemAdd: { preventDefault: true }
});
```

## Publication-related commands

### `goToPage`

Navigate to a specific page: the page number is to be provided as the one and only argument of the function. For example:

```javascript
iPaperAPI.goToPage(4);
```

### `goToPreviousPage`

Go to the previous spread (if flipbook has a spread-based layout), or the previous page (if flipbook has a page-based layout). This method accepts no arguments:

```javascript
iPaperAPI.goToPreviousPage();
```

### `goToNextPage`

Go to the next spread (if flipbook has a spread-based layout), or the next page (if flipbook has a page-based layout). This method accepts no arguments:

```javascript
iPaperAPI.goToNextPage();
```

## Shop-related commands

### `addItem`

Programmatically adds a custom item to the basket. This method accepts a single argument, consisting of properties of the shop item entity.

| Property      | Type     | Description                                |
| ------------- | -------- | ------------------------------------------ |
| `title`       | `string` | Shop item title                            |
| `description` | `string` | Shop item description                      |
| `productID`   | `string` | Shop item product ID                       |
| `originPage`  | `number` | Page number where the shop item is located |
| `price`       | `number` | Shop item price                            |
| `quantity`    | `number` | Number of items added                      |

Example use:

```javascript
iPaperAPI.addItem({
    title: 'My product',
    description: 'Product description',
    productID: 'PROD-25B',
    price: '29.95',
    originPage: 6,
    quantity: 1
});
```

## Advanced use

If you are accustomed to the `.trigger()` regime (such as that implemented in JQuery), you can also use it as such:

```javascript
iPaperAPI.trigger('goToPage', 4);
```

Refer to [**Event listeners and command triggers**](/flipbook-javascript-api/legacy-javascript-api-v2/advanced-usage#event-listeners-and-command-triggers) on the advanced usage page for a wholesome example.


# Events

Events happen in the iframed flipbook and are emitted to the parent window. Our API catches these emitted events and invoke magic callbacks, which can be used to execute business-specific logic in the parent window, in response to specific events that have occurred in the iframed flipbook.

## Overview

![](https://3504352165-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FW0UCgeNLp5UrqOZTjvsN%2Fuploads%2FySiurVIHM8okIG3DU02X%2Fevents.svg?alt=media\&token=93af1c8a-ea9f-49b8-b8e8-3a19e5381c9a)

Events are dispatched from the iframed flipbook to the Javascript API via `window.postMessage()`. Once the API receives this event, it then invokes a callback present in the parent window.

## Publication-related events <a href="#publication-related-events" id="publication-related-events"></a>

### `onPageElementClick`

Fired when an enrichment/link with a shop item action is clicked.

{% hint style="info" %}
**Note:**

Current only shop items will fire this event. Click events on any other enrichments are not emitted.
{% endhint %}

| Property                  | Type       | Description                                                                |
| ------------------------- | ---------- | -------------------------------------------------------------------------- |
| `type`                    | `string`   | The page element type that was clicked on                                  |
| `data`                    | `{}`       | Data associated with the page element                                      |
| `data.title`              | `string`   | Shop item title                                                            |
| `data.description`        | `string`   | Shop item description                                                      |
| `data.productId`          | `string`   | Shop item product ID                                                       |
| `data.price`              | `number`   | Shop item price                                                            |
| `data.amountSelection`    | `boolean`  | If users are allowed to manually specify an amount before adding to basket |
| `data.packageSize`        | `number`   | Number of items per package                                                |
| `event`                   | `{}`       | Event data associated with the click event                                 |
| `event.originPage`        | `number`   | Page number on which the page element is located on                        |
| `event.originSpreadPages` | `number[]` | Page numbers on which the spread where the page element is located on      |
| `event.coordinates.pageX` | `number`   | Absolute x coordinate of the click event                                   |
| `event.coordinates.pageY` | `number`   | Absolute y coordinate of the click event                                   |
| `event.coordinates.bookX` | `number`   | Ratio of the x coordinate relative to book width                           |
| `event.coordinates.bookY` | `number`   | Ratio of the y coordinate relative to book height                          |

### `onSpreadChange`

Fired when a paging event is registered in the flipbook.

| Property              | Type       | Description                                                                                                                                                                                                        |
| --------------------- | ---------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `currentSpreadPages`  | `number[]` | Page numbers that are in the current spread                                                                                                                                                                        |
| `currentVisiblePages` | `number[]` | Page number(s) that are currently in view. This array may contain one or two numbers, depending if the current view is page- (mobile/tablet in portrait) or spread-based (desktop, or mobile/tablet in landscape). |

## Shop-related events

### `onBasketClick`

Fired when the basket icon is clicked to reveal the basket in the sidebar. There is no data returned by this event.

### `onItemAdd`

Fired when a shop item has been added to the basket.

| Property      | Type     | Description                                            |
| ------------- | -------- | ------------------------------------------------------ |
| `title`       | `string` | Shop item title                                        |
| `description` | `string` | Shop item description                                  |
| `productId`   | `string` | Shop item product ID                                   |
| `price`       | `number` | Shop item price                                        |
| `pageIndex`   | `number` | Page index where the shop item is located\* (see note) |
| `originPage`  | `number` | Page number where the shop item is located             |
| `quantity`    | `number` | Number of items added                                  |
| `packageSize` | `number` | Number of items per package                            |

{% hint style="info" %}
**Note:**

Please note that `pageIndex` starts at `0`. So the 1st page of your flipbook will have a `pageIndex` of `0`, page 2 a `pageIndex` of `1` and so on. To get the actual pagenumber you should use `originPage` instead.
{% endhint %}

## Advanced use

If you are accustomed to listening to events instead, that is possible with this API: simply bind the event listener to the iframe element:

```html
<iframe src="https://demo.ipaper.io" id="ipaper-flipbook">
```

The two options presented below are functionally identical.

```javascript
// Basic option: Using magic callbacks to listen to events
iPaperAPI.onSpreadChange = function(data) {
    console.log(data);
};

// Advanced option: Listening to events in the DOM
document.getElementById('ipaper-flipbook').addEventListener('onSpreadChange', function(data) {
    console.log(data);
});
```

Refer to [**Event listeners and command triggers**](/flipbook-javascript-api/legacy-javascript-api-v2/advanced-usage#event-listeners-and-command-triggers) on the advanced usage page for a wholesome example.


# Consent Management

Implement your own cookie consent solution for your Flipbook.

While Flipbooks does have built-in cookie consent management, we understand that some may prefer to use either their own system or a 3rd party system. On this page we have gathered useful code snippets that can assist in implementing custom cookie consent solutions using the v2 Flipbook JavaScript API.

{% hint style="info" %}
**Note:**

Prior to implementing custom cookie solutions, please make sure that your Flipbook cookie policy is set to ‘Integration’.
{% endhint %}

{% hint style="info" %}
**Note:**

When using a 3rd party cookie solution, please make sure that the scripts for the solution are fully loaded before you reference it / use it inside any of the Consent Management API methods. In order to avoid issues, we also recommend that you do not load the 3rd party script asynchronously (i.e. using the async keyword in the tag).
{% endhint %}

## Get current cookie consent

```javascript
async function iPaperInit() {
    await iPaperAPI.getCookieConsent();
}
```

This will return an object that represents the current cookie consent given, like the example below:

```javascript
{
    allowAdvertisingCookies: true
    allowFunctionalCookies: true
    allowPerformanceCookies: true
    allowStrictlyNecessaryCookies: true
    hasPreviousCookieConsent: true
}
```

## Set/Update cookie consent

When setting/updating cookie consent, you can set whether or not (true/false) to allow 3 categories of cookies: Performance, Functional, and Advertising.

```javascript
function iPaperInit() {
    iPaperAPI.updateCookieConsent({ 
        allowPerformanceCookies: true, 
        allowFunctionalCookies: true, 
        allowAdvertisingCookies: true
    });
}
```

## Cookie consent updated callback

Using the code below you can define a callback function that will run when the cookie consent is updated/changed:

```javascript
function iPaperInit() {
    iPaperAPI.onCookieConsentUpdate = function(newConsent) {
        // YOUR CODE GOES HERE
    };
}
```

## Cookie policy callback

Using the code below you can define a callback function that runs when the “Cookie Policy” button/link in the Flipbook viewer header is clicked:

```javascript
function iPaperInit() {
    iPaperAPI.onCookieConsentShown = function() {
        // YOUR CODE GOES HERE
    }
}
```

Please note that by default clicking the “Cookie Policy” button will not do anything if the callback function has not been defined.


# Advanced Usage

## Multiple Flipbooks

In use cases where multiple flipbooks are to be embedded on the same page, small changes to the markup is required so that each individual instances will reference the correct `<iframe>` element without conflict.

The minimal config design of our API means that it will attempt to look for an `<iframe>` containing a flipbook automatically without any additional configuration from the user and create a new instance from said element. When multiple flipbooks are present on the same page, this strategy may not work due to race conditions. Therefore, two actions will be required:

**1. Identify the default flipbook.** The default flipbook (which will be accessible by the `iPaperAPI` variable) using the HTML5 data- attribute, `data-ipaper`:

```html
 <iframe src="https://demo.ipaper.io" data-ipaper></iframe>
```

This iframed flipbook will then be accessible via the globally defined API instance `iPaperAPI`, just like in the single flipbook use case outlined in [quick-start](/flipbook-javascript-api/legacy-javascript-api-v2/quick-start).

{% hint style="warning" %}
**Warning:**

If `data-ipaper` attribute is not added, then the default API instance will simply select the **first** `<iframe>` containing a flipbook that reports back, presenting a race condition. **We cannot guarantee that this is always the first visible flipbook on the page.**
{% endhint %}

**2. Instantiate APIs for subsequent flipbooks.** Subsequent flipbooks will need to have their APIs manually instantiated. The constructor is exposed via the `_iPaperAPI` (note the preceeding underscore character), and a new instance can be created on an `<iframe>` element by passing the element as the first argument:

```javascript
var anotherIPaper = document.getElementById('another-flipbook')
var anotherIPaperAPI = new _iPaperAPI(anotherIPaper);
```

### Example <a href="#example" id="example"></a>

```html
<!doctype html>
<html>
<head>
    <title>Multiple iframed flipbooks</title>

    <!-- Start of async iPaper Flipbook API script -->
    <!-- NOTE: The entire code snippet below can be obtained directly from the Admin. Refer to our help article for further information. -->
    <script>
    (function(i,P,a,p,e,r){if(i.getElementById(a=a+'-'+e))return;
    r=i.querySelector(P).parentNode.appendChild(i.createElement(P));
    r.id=a;r.async=1;r.src=p+'/'+e+'.js'})
    (document,'script','ipaper-api','<ApiBaseUrl>','<ApiSecret>');
    </script>
    <!-- End of async iPaper Flipbook API script -->

    <script>
        function iPaperInit() {
            // Listen to event from the default iframe flipbook
            // This is the default API instance
            iPaperAPI.onSpreadChange = function(data) {
                console.log('onSpreadChange event detected in default flipbook', data);
            };

            // Listen to event from a second iframe flipbook
            // Create new API instance first
            var anotherIPaper = document.getElementById('yet-another-flipbook')
            var anotherIPaperAPI = new _iPaperAPI(anotherIPaper);
            anotherIPaperAPI.onSpreadChange = function(data) {
                console.log('onSpreadChange event detected in second flipbook', data);
            };
        }
    </script>
</head>
<body>
    <!-- Remember to add the HMTL5 `data-ipaper` attribute to the default iframe -->
    <iframe src="https://demo.ipaper.io" style="width: 100%; height: 400px;" data-ipaper></iframe>

    <!-- Subsequent iframes can be identified by any means, but we use ID for convenience -->
    <iframe src="https://demo.ipaper.io" style="width: 100%; height: 400px;" id="yet-another-flipbook"></iframe>
</body>
</html>
```

## Event listeners and command triggers

If you are more accustomed to working with event listeners and command triggers, and do not want to use magic callbacks and commands that are available via the API instance, you can simply:

* add event listeners to the `<iframe>` element, and/or
* trigger commands using `.trigger(<commandName>, <argument>)`

The event name that the event listeners should check with are specified in [events](/flipbook-javascript-api/legacy-javascript-api-v2/events), while command names are specified in [commands](/flipbook-javascript-api/legacy-javascript-api-v2/commands). Mismatched event/command names will simply do nothing.

```html
<!doctype html>
<html>
<head>
    <title>Example iPaper JS API use</title>

    <!-- Start of async iPaper Flipbook API script -->
    <!-- NOTE: The entire code snippet below can be obtained directly from the Admin. Refer to our help article for further information. -->
    <script>
    (function(i,P,a,p,e,r){if(i.getElementById(a=a+'-'+e))return;
    r=i.querySelector(P).parentNode.appendChild(i.createElement(P));
    r.id=a;r.async=1;r.src=p+'/'+e+'.js'})
    (document,'script','ipaper-api','<ApiBaseUrl>','<ApiSecret>');
    </script>
    <!-- End of async iPaper Flipbook API script -->
    
    <script>
        function iPaperInit() {
            // Listen to the `onSpreadChange` event emitted from the <iframe> element
            // Note: This is equivalent to `iPaperAPI.onSpreadChange = function(data) { ... }`;
            document.getElementById('my-ipaper-flipbook').addEventListener('onSpreadChange', function(data) {
                console.log(data);
            });

            // Navigate to page 4 when a button is clicked
            document.getElementById('page-4-button').addEventListener('click', function() {
                // Note: This is equivalent to `iPaperAPI.goToPage(4)`
                iPaperAPI.trigger('goToPage', 4);
            });
        }
    </script>
</head>
<body>
    <iframe
        src="https://demo.ipaper.io"
        style="width: 100%; height: 400px;"
        id="my-ipaper-flipbook"></iframe>
    <button type="button" id="page-4-button">Go to page 4</button>
</body>
</html>
```


# Cookie Information

## Cookies set by iPaper

The following cookies are set when visiting a flipbook on the iPaper platform, we put high effort into ensuring that we follow best practices and actions that could be categorized differently unless the user gives consent.

<table><thead><tr><th width="285">Name</th><th width="161">Value</th><th width="238">Purpose</th><th width="117">SameSite</th><th width="110">HttpOnly</th><th>Secure</th><th width="118">Category</th><th>Expiry</th></tr></thead><tbody><tr><td>ASP.NET_SessionId</td><td>Unique random string</td><td>Session cookie to ensure that the visitor is given access to the respective flipbook, and it's needed assets.</td><td>None</td><td>True</td><td>True</td><td>Strictly necessary</td><td>Session</td></tr></tbody></table>

### Why does the `ASP.NET_SessionId` have SameSite: None?

To ensure our system works correctly when embedded in an iframe or app, we explicitly allow our session cookie to be read by other domains.

If we were to change this, things such as basket content, wouldn't be persisted between page refreshes.

## Cookie categories

If you enable the iPaper cookie banner, or you are using a 3rd party consent management platform through our integration option, this is how the four categories of cookies affect the iPaper solution. Note that we only ever set the cookies defined under “Cookies set by iPaper”, however, their use changes depending on the cookie permissions granted by the user.

### Strictly necessary

This category is always allowed and makes sure the basic iPaper experience works. This is what ensures products stay in the basket and that basic features in the Flipbook works.

### Performance

If we do not get the performance permission, iPaper only tracks basic statistics that are not correlated with the actual session in any way. This is data such as PDF downloads, logo clicks, time of day, etc. With the performance permission granted, iPaper furthermore also tracks page views & time spent.

### Functionality

This category has no impact on the iPaper solution and is only there for future proofing.

### Advertising

All iPaper integrations are disabled if we do not get advertising permission. This includes Google Analytics, Google Tag Manager, Facebook and other integrations.

## Technologies used

As mentioned above in the [#cookies-set-by-ipaper](#cookies-set-by-ipaper "mention") section, we use cookies for storing sessions.

When it comes to the storage of the current cookie category consent given, this is currently stored in [localStorage](https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage). This is however subject to change and could in the future be moved to use a different technology if we deem it more suitable. The technology used may also differ dependent on the browser used.

The stored item in localStorage follows the naming scheme `userCookieConsent_[LicenseID]_[CookieBannerType]` and has a value consisting of an object with the following properties:

<table><thead><tr><th>Name</th><th width="240.33333333333331">Value</th><th>Purpose</th></tr></thead><tbody><tr><td><code>shouldAllowPerformanceCookies</code></td><td><code>boolean</code></td><td>Determines if user has consented to "Performance" cookies</td></tr><tr><td><code>shouldAllowFunctionalCookies</code></td><td><code>boolean</code></td><td>Determines if user has consented to "Functional" cookies</td></tr><tr><td><code>shouldAllowAdvertisingCookies</code></td><td><code>boolean</code></td><td>Determines if user has consented to "Advertising" cookies</td></tr><tr><td><code>expires</code></td><td><code>date</code></td><td>Date when the stored item will expire</td></tr></tbody></table>

{% hint style="info" %}
**Note:**

Please do not attempt to manipulate/update this localStorage value manually, but instead rely on the [JS API for updating consent](/flipbook-javascript-api/commands-and-events/consent).
{% endhint %}


# Common security questions

## Should I security test the Flipbook viewer?

While it's good practice to verify the security of the tools you use, our Flipbook viewer is a hosted solution and therefore has no impact on your other websites.

As such, it does not make sense to run a full suite of security tests against our system, as our Flipbook viewer is designed to conform to the broad security needs of our customer base, rather than the specific requirements for individual customers.

## Why are there no strict CSP headers in the Flipbook viewer?

We offer many ways to enrich our Flipbooks, and many of these allow scripts to be loaded from the internet, such as Google, GTM, and any other script you, as a customer, would like to embed in your flipbook. We also offer a feature called Custom Scripting, where you can write any JavaScript you want, and it'll run when your Flipbook loads.

As such, our CSP header cannot be strict, as it would prevent the above features from working.

## Why we don’t use PFS (Perfect Forward Secrecy)

iPaper is a company with customers in all parts of the world, and as such we're accessed by a broad range of devices. Introducing PFS would prevent older devices from accessing Flipbooks, and with the current device palette, we're seeing accessing our system, we're not at a point where we want to introduce PFS.

## Why is the HSTS header only 7 days

Our system allows customers to set up branded domains for their publications, and we provide SSL certificates as part of this service.

The configuration of branded domains and SSL is managed by the individual companies, and to give our customers some leeway in the configuration of SSL, we've opted for a shorter HSTS header, so there's room for human errors.

## What is the "**IPF\_\_SUPPORT\_TEST\_KEY" key in local storage**

To detect if a device supports Local Storage, we try to set a temporary key called `IPF__SUPPORT_TEST_KEY`. The key is removed immediately after support has been detected.


# Open Source

At iPaper we are fond of Open Source and we do our best to use libraries that are proven and well maintained.

Due to the nature of always keeping an eye on industry standards and most battle-hardened libraries, this list will never be static as we might switch to a more modern or up to date library later on. We do a thorough assesment when we include a new library both in the aspects of their license, their security model and last but not least how well supported is the library.

The list below includes all of the libraries we are currently using and a notation of where in our system they are used. We do not list individual packages for Microsoft .NET Framework as that is core for all of our systems to be running.

## Usage description

| Name     | Description                                                                                                     |
| -------- | --------------------------------------------------------------------------------------------------------------- |
| Backend  | Libraries listed with this usage is used to serve all frontend related output                                   |
| Frontend | These libraries are directly used inside of the visitors browsers                                               |
| Services | The libraries listed here are backend supporting services that could be nightly scheduling, PDF processing etc. |

## Current library usage <a href="#current-library-usage" id="current-library-usage"></a>

| Name                                                    | License             | Repository                                                     | Usage            |
| ------------------------------------------------------- | ------------------- | -------------------------------------------------------------- | ---------------- |
| AjaxMin                                                 | Apache 2.0          | <http://ajaxmin.codeplex.com/>                                 | Backend          |
| Akka                                                    | Apache 2.0          | <https://github.com/akkadotnet/akka.net>                       | Services         |
| Akka.Quartz.Actor                                       | Apache 2.0          | <https://github.com/akkadotnet/Akka.Quartz.Actor>              | Services         |
| Akka.Serialization.Hyperion                             | Apache 2.0          | <https://github.com/akkadotnet/akka.net>                       | Services         |
| AngleSharp                                              | MIT                 | <https://anglesharp.github.io/>                                | Services         |
| animejs                                                 | MIT                 | <https://github.com/juliangarnier/anime>                       | Frontend         |
| Antlr                                                   | BSD                 | <https://github.com/antlr/antlrcs>                             | Backend          |
| autoprefixer                                            | MIT                 | <https://github.com/postcss/autoprefixer>                      | Frontend         |
| aws-sdk                                                 | Apache 2.0          | <https://github.com/aws/aws-sdk-js>                            | Frontend         |
| AWSSDK.CloudWatch                                       | Apache 2.0          | <https://github.com/aws/aws-sdk-net/>                          | Backend          |
| AWSSDK.Core                                             | Apache 2.0          | <https://github.com/aws/aws-sdk-net/>                          | Backend Services |
| AWSSDK.S3                                               | Apache 2.0          | <https://github.com/aws/aws-sdk-net/>                          | Backend Services |
| AWSSDK.SimpleEmail                                      | Apache 2.0          | <https://github.com/aws/aws-sdk-net/>                          | Backend          |
| AWSSDK.SimpleNotificationService                        | Apache 2.0          | <https://github.com/aws/aws-sdk-net/>                          | Backend          |
| AWSSDK.SQS                                              | Apache 2.0          | <https://github.com/aws/aws-sdk-net/>                          | Backend Services |
| backbone                                                | MIT                 | <https://github.com/jashkenas/backbone>                        | Frontend         |
| cache-loader                                            | MIT                 | <https://github.com/webpack-contrib/cache-loader>              | Frontend         |
| Certes                                                  | MIT                 | <https://github.com/fszlin/certes>                             | Backend          |
| clipboard                                               | MIT                 | <https://github.com/zenorocha/clipboard.js>                    | Frontend         |
| css-loader                                              | MIT                 | <https://github.com/webpack-contrib/css-loader>                | Frontend         |
| cssnano                                                 | MIT                 | <https://github.com/cssnano/cssnano>                           | Frontend         |
| CsvHelper                                               | MS-PL OR Apache 2.0 | <https://joshclose.github.io/CsvHelper/>                       | Backend          |
| direct-vuex                                             | CC0-1.0             | <https://github.com/paroi-tech/direct-vuex>                    | Frontend         |
| DnsClient                                               | Apache 2.0          | <http://dnsclient.michaco.net/>                                | Backend          |
| dotless                                                 | Apache 2.0          | <https://github.com/dotless/dotless>                           | Backend          |
| EPPlus                                                  | LGPL                | <https://epplussoftware.com/>                                  | Services Backend |
| exports-loader                                          | MIT                 | <https://github.com/webpack-contrib/exports-loader>            | Frontend         |
| Expressmapper                                           | Apache 2.0          | <http://expressmapper.org/>                                    | Backend          |
| file-loader                                             | MIT                 | <https://github.com/webpack-contrib/file-loader>               | Frontend         |
| fork-ts-checker-webpack-plugin                          | MIT                 | <https://github.com/TypeStrong/fork-ts-checker-webpack-plugin> | Frontend         |
| fscreen                                                 | MIT                 | <https://github.com/rafgraph/fscreen>                          | Frontend         |
| hammerjs                                                | MIT                 | <https://github.com/hammerjs/hammer.js>                        | Frontend         |
| Hashids.net                                             | MIT                 | <https://github.com/ullmark/hashids.net>                       | Backend          |
| HtmlAgilityPack                                         | MIT                 | <http://html-agility-pack.net/>                                | Backend Services |
| Intercom.Dotnet.Client                                  | Apache 2.0          | <https://github.com/intercom/intercom-dotnet>                  | Backend          |
| iTextSharp.LGPLv2.Core                                  | LGPL 2.0            | <https://github.com/VahidN/iTextSharp.LGPLv2.Core>             | Backend Services |
| Jint                                                    | BSD-2-Clause        | <https://github.com/sebastienros/jint>                         | Backend          |
| jquery                                                  | MIT                 | <https://github.com/jquery/jquery>                             | Frontend         |
| js-cookie                                               | MIT                 | <https://github.com/js-cookie/js-cookie>                       | Frontend         |
| lodash.clonedeep                                        | MIT                 | <https://github.com/lodash/lodash>                             | Frontend         |
| lodash.debounce                                         | MIT                 | <https://github.com/lodash/lodash>                             | Frontend         |
| lodash.merge                                            | MIT                 | <https://github.com/lodash/lodash>                             | Frontend         |
| lodash.mergewith                                        | MIT                 | <https://github.com/lodash/lodash>                             | Frontend         |
| Markdig                                                 | BSD-2-Clause        | <https://github.com/lunet-io/markdig>                          | Backend          |
| MaxMind.GeoIP2                                          | Apache 2.0          | <https://github.com/maxmind/GeoIP2-dotnet>                     | Backend          |
| mini-css-extract-plugin                                 | MIT                 | <https://github.com/webpack-contrib/mini-css-extract-plugin>   | Frontend         |
| Newtonsoft.Json                                         | MIT                 | <https://www.newtonsoft.com/json>                              | Backend          |
| NHttp                                                   | LGPL                | <http://github.com/pvginkel/NHttp>                             | Services         |
| Pipelines.Sockets.Unofficial                            | MIT                 | <https://github.com/mgravell/Pipelines.Sockets.Unofficial>     | Backend          |
| portal-vue                                              | MIT                 | <https://github.com/LinusBorg/portal-vue>                      | Frontend         |
| postcss-loader                                          | MIT                 | <https://github.com/postcss/postcss-loader>                    | Frontend         |
| PreMailer.Net                                           | MIT                 | <https://github.com/milkshakesoftware/PreMailer.Net>           | Backend          |
| qrcode-svg                                              | MIT                 | <https://github.com/papnkukn/qrcode-svg>                       | Frontend         |
| Quartz                                                  | Apache 2.0          | <https://www.quartz-scheduler.net/>                            | Services         |
| query-string                                            | MIT                 | <https://github.com/sindresorhus/query-string>                 | Frontend         |
| quill                                                   | BSD-3-Clause        | <https://github.com/quilljs/quill>                             | Frontend         |
| quill-delta                                             | MIT                 | <https://github.com/quilljs/delta>                             | Frontend         |
| R7Insight.Core                                          | MIT                 | <https://github.com/rapid7/r7insight_dotnet/>                  | Services Backend |
| RestSharp                                               | Apache 2.0          | <http://restsharp.org/>                                        | Backend          |
| sass                                                    | MIT                 | <https://github.com/sass/dart-sass>                            | Frontend         |
| sass-loader                                             | MIT                 | <https://github.com/webpack-contrib/sass-loader>               | Frontend         |
| sortablejs                                              | MIT                 | <https://github.com/SortableJS/Sortable>                       | Frontend         |
| StackExchange.Redis                                     | MIT                 | <https://github.com/StackExchange/StackExchange.Redis/>        | Backend          |
| Stripe.net                                              | Apache 2.0          | <https://github.com/stripe/stripe-dotnet>                      | Backend          |
| svgo                                                    | MIT                 | <https://github.com/svg/svgo>                                  | Frontend         |
| svgo-loader                                             | MIT                 | <https://github.com/pozadi/svgo-loader>                        | Frontend         |
| terser-webpack-plugin                                   | MIT                 | <https://github.com/webpack-contrib/terser-webpack-plugin>     | Frontend         |
| tinycolor2                                              | MIT                 | <https://bgrinshub.com/TinyColor>                              | Frontend         |
| Topshelf                                                | Apache 2.0          | <https://github.com/Topshelf/Topshelf>                         | Services         |
| ts-keycode-enum                                         | MIT                 | <https://github.com/nfriend/ts-keycode-enum>                   | Frontend         |
| ts-loader                                               | MIT                 | <https://github.com/TypeStrong/ts-loader>                      | Frontend         |
| tslib                                                   | Apache 2.0          | <https://github.com/Microsoft/tslib>                           | Frontend         |
| tween.js                                                | MIT                 | <https://github.com/tweenjs/tween.js>                          | Frontend         |
| TwoFactorAuth.Net                                       | MIT                 | <https://github.com/RobThree/TwoFactorAuth.Net>                | Backend          |
| typescript                                              | Apache 2.0          | <https://github.com/Microsoft/TypeScript>                      | Frontend         |
| underscore                                              | MIT                 | <https://github.com/jashkenas/underscore>                      | Frontend         |
| unQuery                                                 | MIT                 | <https://github.com/improvedk/unQuery>                         | Backend Services |
| uuid                                                    | MIT                 | <https://github.com/uuidjs/uuid>                               | Frontend         |
| vue                                                     | MIT                 | <https://github.com/vuejs/vue>                                 | Frontend         |
| vue-class-component                                     | MIT                 | <https://github.com/vuejs/vue-class-component>                 | Frontend         |
| vue-implicit-css-modules                                | MIT                 | <https://github.com/AjiTae/vue-implicit-css-modules>           | Frontend         |
| vue-loader                                              | MIT                 | <https://github.com/vuejs/vue-loader>                          | Frontend         |
| vue-property-decorator                                  | MIT                 | <https://github.com/kaorun343/vue-property-decorator>          | Frontend         |
| vue-router                                              | MIT                 | <https://github.com/vuejs/vue-router>                          | Frontend         |
| vue-style-loader                                        | MIT                 | <https://github.com/vuejs/vue-style-loader>                    | Frontend         |
| vue-svg-loader                                          | MIT                 | <https://github.com/visualfanatic/vue-svg-loader>              | Frontend         |
| vue-template-compiler                                   | MIT                 | <https://github.com/vuejs/vue>                                 | Frontend         |
| vuedraggable                                            | MIT                 | <https://github.com/SortableJS/Vue.Draggable>                  | Frontend         |
| vuex                                                    | MIT                 | <https://github.com/vuejs/vuex>                                | Frontend         |
| vuex-class                                              | MIT                 | <https://github.com/ktsn/vuex-class>                           | Frontend         |
| webfontloader                                           | Apache 2.0          | <https://github.com/typekit/webfontloader>                     | Frontend         |
| webpack                                                 | MIT                 | <https://github.com/webpack/webpack>                           | Frontend         |
| @vuelidate/core                                         | MIT                 | <https://github.com/vuelidate/vuelidate>                       | Frontend         |
| @vuelidate/validators                                   | MIT                 | <https://github.com/vuelidate/vuelidate>                       | Frontend         |
| luxon                                                   | MIT                 | <https://github.com/moment/luxon>                              | Frontend         |
| @intervolga/optimize-cssnano-plugin                     | MIT                 | <https://github.com/intervolga/optimize-cssnano-plugin>        | Frontend         |
| mini-css-extract-plugin                                 | MIT                 | <https://github.com/webpack-contrib/mini-css-extract-plugin>   | Frontend         |
| query-string-for-all                                    | MIT                 | <https://www.npmjs.com/package/query-string-for-all>           | Frontend         |
| @tweenjs/tween.js                                       | MIT                 | <https://github.com/tweenjs/tween.js>                          | Frontend         |
| @vimeo/player                                           | Apache 2.0          | <https://github.com/vimeo/player.js>                           | Frontend         |
| @webcomponents/custom-elements                          | MIT                 | <https://github.com/webcomponents/polyfills>                   | Frontend         |
| construct-style-sheets-polyfill                         | MIT                 | <https://github.com/calebdwilliams/construct-style-sheets>     | Frontend         |
| r7insight\_js                                           | MIT                 | <https://github.com/rapid7/r7insight_js>                       | Frontend         |
| zapier-platform-core                                    | Apache 2.0          | <https://github.com/zapier/zapier-platform>                    | Services         |
| ZiggyCreatures.FusionCache                              | MIT                 | <https://github.com/jodydonetti/ZiggyCreatures.FusionCache>    | Backend          |
| ZiggyCreatures.FusionCache.Serialization.NewtonsoftJson | MIT                 | <https://github.com/jodydonetti/ZiggyCreatures.FusionCache>    | Backend          |
| KubernetesClient                                        | Apache 2.0          | <https://github.com/kubernetes-client/csharp>                  | Backend          |
| Octokit                                                 | MIT                 | <https://github.com/octokit/octokit.net>                       | Backend          |
| SixLabors.Fonts                                         | Apache 2.0          | <https://github.com/SixLabors/Fonts>                           | Backend          |
| SixLabors.ImageSharp                                    | Apache 2.0          | <https://github.com/SixLabors/ImageSharp>                      | Backend          |
| SixLabors.ImageSharp.Drawing                            | Apache 2.0          | <https://github.com/SixLabors/ImageSharp.Drawing>              | Backend          |
| NuGet.Core                                              | Apache 2.0          | <https://github.com/NuGet/NuGetGallery>                        | Backend          |
| Scriban                                                 | MIT                 | <https://github.com/scriban/scriban>                           | Backend          |
| Argotic.Core                                            | Apache 2.0          | <https://github.com/argotic-syndication-framework/Argotic>     | Backend          |
| Ctl.Data                                                | MIT                 | <https://github.com/segmentio/ctlstore>                        | Backend          |
| EcmaScript.Net                                          | MIT                 | <https://github.com/PureKrome/EcmaScript.NET>                  | Backend          |
| iTextSharp-LGPL                                         | LGPL 2.1            | <https://github.com/itext/itextsharp>                          | Backend          |
| logentries.core                                         | MIT                 | <https://github.com/logentries/le_dotnet>                      | Backend          |
| Zyborg.Logentries                                       | MIT                 | <https://github.com/zyborg/LogentriesDotNet>                   | Backend          |
| FileHelpers                                             | MIT                 | <https://github.com/MarcosMeli/FileHelpers>                    | Backend          |
| LumenWorksCsvReader                                     | MIT                 | <https://github.com/phatcher/CsvReader>                        | Backend          |
| StronglyTypedId                                         | MIT                 | <https://github.com/andrewlock/StronglyTypedId>                | Backend          |
| LumenWorks.Framework.IO                                 | MIT                 | <https://github.com/phatcher/CsvReader>                        | Backend          |
| WebGrease                                               | Apache 2.0          | <https://github.com/TerribleDev/WebGrease>                     | Backend          |
| AWSSDK.SecretsManager.Caching                           | Apache 2.0          | <https://github.com/aws/aws-secretsmanager-caching-net>        | Backend          |
| MudBlazor                                               | MIT                 | <https://github.com/MudBlazor/MudBlazor>                       | Frontend         |
| DarkUIReborn                                            | MIT                 | <https://github.com/Sgridev/DarkUIReborn>                      | Frontend         |
| jacobslusser.ScintillaNET                               | BSD-3-Clause        | <https://github.com/jacobslusser/ScintillaNET>                 | Frontend         |
| StackExchange.Redis.StrongName                          | MIT                 | <https://github.com/StackExchange/StackExchange.Redis>         | Backend          |


