Flipazine API
Upload PDFs and manage flipbooks programmatically — built for integrations like publishing a flipbook from a content management system (e.g. MemberGate) and embedding it on a page.
Base URL: https://flipazine.com/api/v1
Every endpoint is listed below with its full path. All responses are JSON and wrap their payload in a data property (except the plain { "success": true } acknowledgements and error objects).
Authentication
Every request needs an API key in the Authorization header. Create one from Account → API keys — the raw key is shown once at creation time and can't be retrieved again after that.
Authorization: Bearer fz_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxAPI access is a plan feature — a valid key still returns 403 if the account's current plan doesn't include API access. Team members' keys act on their team's shared account, not a separate one of their own. Check capabilities.apiAccess from GET /account to confirm before relying on the API.
Rate limits
Each account gets a per-minute request budget shared across all of its API keys — set by the account's plan, and occasionally raised for specific integration partners on request. Every response includes headers showing where you stand:
X-RateLimit-Limit: 120
X-RateLimit-Remaining: 117
X-RateLimit-Reset: 2026-07-03T14:32:00.000ZGoing over the limit returns 429 Too Many Requests with the same headers — back off until X-RateLimit-Reset. Need a higher limit for a high-volume integration? Get in touch.
Response shape & fields
A successful call returns { "data": ... }. For a single flipbook data is a flipbook object; for the list it's an array of them. Every flipbook object contains these fields:
| Field | Type | Notes |
|---|---|---|
id | string | The flipbook's unique id — used in every /flipbooks/{id} path and in its URLs |
title | string | |
description | string | null | |
originalFilename | string | The uploaded file's name |
status | string | pending → processing → ready, or failed |
errorMessage | string | null | Set only when status is failed |
pageCount | number | 0 until processing finishes |
isPublic | boolean | Public flipbooks are viewable and embeddable by anyone; private ones only where allowed (see Embedding) |
viewCount | number | Total views (excludes the owner previewing) |
allowSearch, allowFullscreen, allowShare, allowSound, allowDownload, allowContents, allowPrint, allowAutoplay | boolean | Viewer feature toggles — see Upload |
layout | string | One of the six layouts |
coverStyle | string | hard or soft — only affects the page-turning layouts |
embedDomains | string[] | Hostnames a private flipbook may be embedded on (see Embedding) |
pdfUrl | string | null | The original uploaded PDF. null until status is ready. See The original PDF |
thumbnailUrl | string | null | Cover-page thumbnail image. null until ready |
viewerUrl | string | Shareable full page (/f/{id}) — link to it, don't frame it |
embedUrl | string | Bare flipbook (/embed/{id}) — what the iframe points at |
embedCode | string | Ready-made <iframe> snippet for embedUrl |
createdAt, updatedAt | string | ISO 8601 timestamps |
List vs. single: GET /flipbooks/{id} additionally includes pages, links and outline arrays (the page images, in-page hyperlinks and table of contents). The list endpoint omits those for brevity. Responses may also carry internal storage keys (pdfKey, thumbnailKey); prefer pdfUrl / thumbnailUrl, which are the fully-qualified URLs.
Account, plan & defaults
GET /account — what the account is allowed to do, what it's using, and what its new flipbooks start with. Use capabilities to decide what to offer before you try (rather than attempting a call and handling a 403).
curl https://flipazine.com/api/v1/account \
-H "Authorization: Bearer fz_..."{
"data": {
"email": "publisher@example.com",
"plan": { "key": "pro", "name": "Pro" },
"subscription": { "status": "active", "currentPeriodEnd": "2026-08-01T00:00:00.000Z" },
"capabilities": {
"apiAccess": true,
"replacePdf": true, // whether POST /flipbooks/{id}/replace will work
"monthlyFlipbookLimit": 100, // -1 means unlimited
"storageLimitMB": 5000,
"maxTeamMembers": 5,
"apiRateLimitPerMinute": 120
},
"usage": {
"flipbooks": { "used": 12, "limit": 100 },
"storageBytes":{ "used": 734003200, "limitBytes": 5242880000 },
"teamMembers": { "used": 2, "limit": 5 },
"apiAccess": true
},
"flipbookDefaults": {
"isPublic": false,
"layout": "spread", "coverStyle": "hard",
"allowSearch": true, "allowContents": true, "allowFullscreen": true,
"allowShare": true, "allowPrint": true, "allowDownload": false,
"allowSound": true, "allowAutoplay": false,
"embedDomains": ["members.example.com"]
}
}
}flipbookDefaults are the settings a new flipbook gets when the upload doesn't say otherwise — mirror them in your own form so what you show matches what you'd actually create. Change them with PATCH /account (only affects flipbooks created afterwards; existing ones are untouched):
curl -X PATCH https://flipazine.com/api/v1/account \
-H "Authorization: Bearer fz_..." \
-H "Content-Type: application/json" \
-d '{"allowDownload": false, "embedDomains": ["members.example.com"]}'Accepts any of the same fields as the defaults object above; send only the ones you want to change. The body may be the fields directly (as shown) or wrapped in a flipbookDefaults object. Returns the updated defaults.
List flipbooks
GET /flipbooks
curl https://flipazine.com/api/v1/flipbooks \
-H "Authorization: Bearer fz_..."Returns every flipbook on the account, most recent first.
{
"data": [
{
"id": "clx1a2b3c...",
"title": "Spring Catalogue",
"description": null,
"status": "ready",
"pageCount": 24,
"isPublic": true,
"embedDomains": [],
"layout": "spread",
"coverStyle": "hard",
"allowSearch": true,
"allowDownload": false,
"pdfUrl": "https://cdn.flipazine.com/pdfs/clx1a2b3c....pdf",
"thumbnailUrl": "https://cdn.flipazine.com/thumbs/clx1a2b3c....png",
"viewerUrl": "https://flipazine.com/f/clx1a2b3c...",
"embedUrl": "https://flipazine.com/embed/clx1a2b3c...",
"embedCode": "<iframe src=\"https://flipazine.com/embed/clx1a2b3c...\" width=\"100%\" height=\"700\" style=\"border:none;\" allow=\"fullscreen\" allowfullscreen></iframe>",
"createdAt": "2026-06-01T10:00:00.000Z",
"updatedAt": "2026-06-01T10:03:00.000Z"
/* ...plus every other field from the reference above */
}
]
}Upload a PDF (create a flipbook)
POST /flipbooks — multipart/form-data
Every field except title and pdfFile is optional. Anything you omit falls back to the account's default for that setting (see flipbookDefaults), not a fixed value — so the effective default is whatever GET /account reports. The "Account default" column below shows the out-of-the-box value on a fresh account.
| Field | Required | Account default | Notes |
|---|---|---|---|
title | Yes | — | Flipbook title |
pdfFile | Yes | — | The PDF file |
description | No | — | |
isPublic | No | false | "true"/"false". When private, set embedDomains to allow embedding |
allowSearch | No | true | Full-text search |
allowContents | No | true | Table-of-contents panel |
allowFullscreen | No | true | Fullscreen button |
allowShare | No | true | Share / copy-link button |
allowPrint | No | true | Print button |
allowDownload | No | false | In-viewer button to download the original PDF |
allowSound | No | true | Page-turn sound |
allowAutoplay | No | false | Auto-advancing slideshow |
layout | No | spread | One of the six layouts |
coverStyle | No | hard | hard/soft — only affects page-turning layouts |
embedDomains | No | account default | Comma-, space- or newline-separated hostnames (max 20). See Embedding |
curl https://flipazine.com/api/v1/flipbooks \
-H "Authorization: Bearer fz_..." \
-F "title=Spring Catalogue" \
-F "pdfFile=@catalogue.pdf" \
-F "isPublic=false" \
-F "embedDomains=members.example.com, www.example.com"Returns 201 immediately with status: "pending" — the PDF is converted to a flipbook in the background. Poll GET /flipbooks/{id} until it's ready.
Get a flipbook / poll status
GET /flipbooks/{id}
A flipbook moves through pending → processing → ready (or failed). Poll this endpoint every few seconds after upload until status is "ready", then use viewerUrl, embedCode or pdfUrl from the response. This response also includes the pages, links and outline arrays (see the field reference).
curl https://flipazine.com/api/v1/flipbooks/clx1a2b3c... \
-H "Authorization: Bearer fz_..."Update a flipbook's settings
PATCH /flipbooks/{id} — application/json. Change settings without touching the PDF. Send only the fields you want to change; anything you omit is left alone. Returns the updated flipbook and takes effect immediately — no reprocessing.
curl -X PATCH https://flipazine.com/api/v1/flipbooks/clx1a2b3c... \
-H "Authorization: Bearer fz_..." \
-H "Content-Type: application/json" \
-d '{"allowDownload": false, "embedDomains": ["members.example.com", "www.example.com"]}'Accepts any of:
| Field | Type | Notes |
|---|---|---|
title | string | 1–255 chars |
description | string | null | Up to 2000 chars; null clears it |
isPublic | boolean | |
allow* | boolean | Any of the eight viewer toggles |
layout | string | One of the six layouts |
coverStyle | string | hard / soft |
embedDomains | string[] | Array of hostnames (max 20). Normalised server-side — scheme/path/port stripped, lowercased, de-duplicated |
Table of contents
A flipbook's contents are read from the PDF on upload — from its bookmarks if it has them, otherwise by parsing a printed contents page. That parse is a heuristic, so it can pick up stray text or miss entries. These endpoints let you correct it.
GET /flipbooks/{id}/outline — the current table, in display order:
curl https://flipazine.com/api/v1/flipbooks/clx1a2b3c.../outline \
-H "Authorization: Bearer fz_..."
# → { "data": [ { "title": "Editor's Message", "pageNumber": 3, "depth": 0 }, ... ] }PUT /flipbooks/{id}/outline replaces the whole table — array order is the display order:
curl -X PUT https://flipazine.com/api/v1/flipbooks/clx1a2b3c.../outline \
-H "Authorization: Bearer fz_..." \
-H "Content-Type: application/json" \
-d '{"entries": [
{"title": "Editor Message", "pageNumber": 3, "depth": 0},
{"title": "Case Report", "pageNumber": 4, "depth": 0},
{"title": "Method", "pageNumber": 5, "depth": 1}
]}'depth (0–3) indents an entry under the one above. title is 1–300 chars, pageNumber must be within the document, and there's a limit of 500 entries. Send {"entries": []} to clear the contents entirely. Returns the saved table.
POST /flipbooks/{id}/outline re-reads the contents from the stored PDF. This discards any edits — it's the "start again from what the PDF says" button:
curl -X POST https://flipazine.com/api/v1/flipbooks/clx1a2b3c.../outline \
-H "Authorization: Bearer fz_..."The rescan runs in the background; poll GET to see the result. Note that replacing a flipbook's PDF does not touch its contents — a scan is a guess, and it would be wrong to overwrite a corrected table with a fresh guess on every new edition. Rescan explicitly if you want the new PDF's contents.
Replace the PDF
POST /flipbooks/{id}/replace — multipart/form-data with a single pdfFile field. Upload a new edition of an existing flipbook. The flipbook keeps its id, so its viewerUrl, embedUrl and every page it's already embedded on keep working and simply start showing the new version. Requires a plan that includes PDF replacement (capabilities.replacePdf); otherwise returns 403.
curl -X POST https://flipazine.com/api/v1/flipbooks/clx1a2b3c.../replace \
-H "Authorization: Bearer fz_..." \
-F "pdfFile=@march-2026-issue.pdf"Conversion runs in the background exactly as it does for a new upload: the flipbook goes back to status: "processing" and keeps serving the previous edition until the new one is ready, so poll GET /flipbooks/{id} until it reads ready again. Once ready, pdfUrl points at the new edition.
Replacing doesn't count against your monthly flipbook allowance — it isn't a new flipbook. The previous edition's pages are removed as part of the swap, so its storage is released too.
Delete a flipbook
DELETE /flipbooks/{id} — permanent; removes the flipbook and its stored files.
curl -X DELETE https://flipazine.com/api/v1/flipbooks/clx1a2b3c... \
-H "Authorization: Bearer fz_..."
# → { "success": true }Search inside a flipbook
GET /flipbooks/{id}/search?q=your+search+term
curl "https://flipazine.com/api/v1/flipbooks/clx1a2b3c.../search?q=pricing" \
-H "Authorization: Bearer fz_..."Returns matches with a snippet and the position of the hit, ordered by page:
{
"data": [
{
"pageNumber": 12,
"snippet": "...our 2026 pricing is held flat for existing...",
"matchStart": 9,
"matchLength": 7,
"wholeWord": true,
"rects": [ { "xPct": 12.4, "yPct": 30.1, "widthPct": 8.0, "heightPct": 2.2 } ]
}
]
}rects give the match's location on the page as percentages (empty if the flipbook was processed before positions were captured, or the page has no text layer).
Layouts
The layout field (settable on upload and via PATCH) picks how the flipbook is displayed:
| Value | Description | Cover style? |
|---|---|---|
spread | Two pages side by side with 3D page turns (drops to a single page when there isn't room). The default. | Yes |
single | One page at a time with 3D page turns. | Yes |
scroll | Pages stacked vertically, scrolled like a document. Best for long reads and small screens. | No |
slides | One page at a time, sliding across. Suits decks, posters and menus. | No |
carousel | A 3D carousel with neighbouring pages angled either side. | No |
rolodex | The carousel turned on its side — pages stacked above and below. | No |
coverStyle (hard/soft) only affects the page-turning layouts (spread, single); it's stored but has no visible effect on the others.
The original PDF
Every flipbook response includes pdfUrl — a direct link to the original PDF that was uploaded (or, after a replace, the current edition). It's null until the flipbook's status is ready, since the original is stored during background processing.
pdfUrl is returned to your (authenticated) integration regardless of the allowDownload setting — that toggle only controls whether end viewers see a download button in the flipbook itself. Treat pdfUrl as the source-of-truth link to the original document; pair it with thumbnailUrl for the cover image.
Embedding a flipbook
Once a flipbook is ready, drop the embedCode from the API response straight into a page — for example a MemberGate content page:
Note the two different URLs. viewerUrl (/f/…) is the shareable page, with the site header, title and description around the flipbook — link to it, don't frame it. embedUrl (/embed/…) is the flipbook on its own, with no Flipazine chrome, and it's what embedCode points at.
Public vs. private
A public flipbook (isPublic: true) can be embedded anywhere and opened directly.
A private flipbook (isPublic: false) is not viewable by a direct link and not listed anywhere — but it can be embedded on the hostnames you list in embedDomains. When the embed loads, the browser tells Flipazine which page framed it (via the Referer header); if that hostname is on the flipbook's embedDomains list, the flipbook displays, otherwise the frame shows a 404. This is what lets a members-only publication live on a members' site without being readable, findable or embeddable anywhere else.
| To embed a private flipbook, the hosting page must… |
|---|
Be served from a hostname on the flipbook's embedDomains (exact match, case-insensitive — no wildcards; list www and bare domains and each subdomain separately) |
| Be served over HTTPS |
Send a referrer — the browser default is fine, but a Referrer-Policy: no-referrer or same-origin on the page will break the embed |
Set embedDomains on upload, per-flipbook via PATCH, or account-wide as a default via PATCH /account. This is an access-scoping control, not hard DRM — the page images are served from a CDN, so it stops casual sharing and off-site embedding rather than a determined downloader who already has the id.
Background colour
The embed has a white background by default, so it sits neutrally in your page. Override it with a bg query param — a hex colour, with or without the #:
<iframe
src="https://flipazine.com/embed/clx1a2b3c...?bg=f5f5f5"
width="100%" height="700" style="border:none;"
allow="fullscreen" allowfullscreen></iframe>Anything that isn't a valid hex colour is ignored and the default is used. The viewer's controls switch between light and dark ink automatically to stay readable on the colour you pick.
Iframe height
The flipbook fits whatever height you give it. The viewer measures the frame and scales the book to fit both its width and its height, so a fixed height="700" won't overflow or produce inner scrollbars — the book is simply drawn smaller. You don't have to work out a per-flipbook height.
If you'd rather the frame matched the flipbook's natural size, the embed posts the height it would like to the parent window whenever that changes:
window.addEventListener("message", (event) => {
if (event.origin !== "https://flipazine.com") return;
const msg = event.data;
if (msg?.type !== "flipazine:resize") return;
// msg.flipbookId — which flipbook, if you embed more than one
// msg.height — the height it would like, in px
document.querySelector("#my-flipbook-iframe").style.height = msg.height + "px";
});The reported height is the flipbook's natural height at the frame's current width. Applying it settles immediately: the frame becomes exactly tall enough, the book renders at full size, and the next message reports the same value.
Pages stop shrinking at a readable minimum, so a very short frame (under roughly 400px for a portrait page) can still overflow — a book too small to read is worse than a scrollbar. Always check event.origin before acting on a message.
Errors
Errors are always a JSON object with an error message and an appropriate status code:
{ "error": "Invalid or missing API key" }| Status | Meaning |
|---|---|
400 | Missing/invalid fields, or the uploaded file isn't a valid PDF |
401 | Missing or invalid API key |
403 | Plan doesn't include API access or PDF replacement, or a plan limit (flipbook count / storage) has been reached |
404 | Flipbook not found, or not owned by this key |
413 | File too large |
429 | Rate limit exceeded — see X-RateLimit-Reset header |
Full walkthrough
A typical integration — e.g. publishing from a CMS and embedding the result on a members' content page:
# 1. (Optional) Check what the account can do and its defaults
curl https://flipazine.com/api/v1/account -H "Authorization: Bearer fz_..."
# 2. Upload the PDF — private, embeddable only on the members' site
curl https://flipazine.com/api/v1/flipbooks \
-H "Authorization: Bearer fz_..." \
-F "title=Spring Catalogue" \
-F "pdfFile=@catalogue.pdf" \
-F "isPublic=false" \
-F "embedDomains=members.example.com"
# → { "data": { "id": "clx1a2b3c...", "status": "pending", ... } }
# 3. Poll until ready (repeat every few seconds)
curl https://flipazine.com/api/v1/flipbooks/clx1a2b3c... \
-H "Authorization: Bearer fz_..."
# → { "data": { "status": "ready", "embedCode": "<iframe ...>", "pdfUrl": "https://...", ... } }
# 4. Render embedCode on the members' page (served from members.example.com over HTTPS)Questions or need a rate limit raised for a high-volume integration? Get in touch.