Quick start
Make your first API call in under 2 minutes.
No SDK needed — just fetch.
Go to Dashboard → select your site → API.
Your public key (pnd_pub_...) is already generated.
Copy it.
It's in the URL when you're in the dashboard: dashboard/cms?site=YOUR_SITE_ID
const SITE_ID = 'YOUR_SITE_ID'; const PUB_KEY = 'pnd_pub_YOUR_KEY'; const BASE_URL = 'https://api.foundiq.nl/v1'; function headers() { return { 'Authorization': `Bearer $${PUB_KEY}` }; } // 1. Fetch all collections const res = await fetch(`$${BASE_URL}/sites/$${SITE_ID}/collections`, { headers: headers() }); const { data: collections } = await res.json(); // 2. Fetch entries in a collection const res2 = await fetch(`$${BASE_URL}/sites/$${SITE_ID}/collections/products/entries`, { headers: headers() }); const { data: products, meta } = await res2.json(); console.log(`Loaded $${products.length} of $${meta.total} products`);
{
"ok": true,
"data": [
{ "id": "...", "status": "published", "data": { "name": "Air Max 90", "price": 120 } },
...
],
"meta": { "total": 42, "limit": 20, "offset": 0, "has_more": true }
}Introduction
The Foundiq delivery API lets you fetch your content from any frontend — Next.js, SvelteKit, vanilla JS, mobile apps, anywhere that can make an HTTP request. It's read-optimised, CORS-enabled, and cacheable by default.
Authentication
Every request requires a Bearer token in the Authorization header.
Foundiq issues two types of token per site — choose the right one for your context.
| Key type | Prefix | Where to use | Permissions |
|---|---|---|---|
| Public | pnd_pub_ | Browser JS, mobile apps, public frontends | Read published content, decrement stock |
| Secret | pnd_sec_ | Server-side only — never in client code | All public permissions + write entries |
Authorization: Bearer pnd_pub_your_key_here
Base URL
All API requests are made to:
https://api.foundiq.nl/v1
Collections
Collections are the content types you define in the CMS — products, blog posts, FAQs etc. Returns all collections with published entry counts.
/v1/sites/:siteId/collections Publicfetch('https://api.foundiq.nl/v1/sites/SITE_ID/collections', { headers: { 'Authorization': 'Bearer pnd_pub_YOUR_KEY' } })
{
"ok": true,
"data": [
{
"id": "bbc36e1f-...", "name": "products",
"label": "Products", "color": "#00d4ff",
"fields": [...], "entry_count": 42
}
]
}List entries
Returns published entries in a collection. Supports filtering, search, and pagination. Drafts are never returned.
/v1/sites/:siteId/collections/:name/entries Public| Parameter | Type | Default | Description |
|---|---|---|---|
limit | integer | 20 | Max entries. Maximum 100. |
offset | integer | 0 | Entries to skip. |
order | string | created_at | created_at or updated_at |
dir | string | desc | asc or desc |
search | string | — | Full-text search across all data fields. |
[field] | string | — | Filter by any data field. e.g. ?category=shoes |
// Fetch 10 products in the "shoes" category fetch('https://api.foundiq.nl/v1/sites/SITE_ID/collections/products/entries?limit=10&category=shoes', { headers: { 'Authorization': 'Bearer pnd_pub_YOUR_KEY' } })
{
"ok": true,
"data": [
{
"id": "191b2302-...", "status": "published",
"data": { "name": "Air Max 90", "price": 120, "stock": 8 },
"created_at": "2025-05-01T10:00:00Z"
}
],
"meta": { "total": 42, "limit": 10, "offset": 0, "has_more": true }
}Single entry
Returns a single published entry by ID.
/v1/sites/:siteId/collections/:name/entries/:entryId Publicfetch('https://api.foundiq.nl/v1/sites/SITE_ID/collections/products/entries/ENTRY_ID', { headers: { 'Authorization': 'Bearer pnd_pub_YOUR_KEY' } })
Create entry
Write an entry from an external system — contact forms, newsletter signups, order records, reviews, or any structured data you want in the CMS. Requires a secret key. Collection is auto-created if it doesn't exist.
/v1/sites/:siteId/collections/:name/entries Secret| Field | Type | Required | Description |
|---|---|---|---|
data | object | ✓ | Your entry data — any JSON object. |
status | string | "published" (default) or "draft" |
await fetch('https://api.foundiq.nl/v1/sites/SITE_ID/collections/contact/entries', { method: 'POST', headers: { 'Authorization': 'Bearer pnd_sec_YOUR_SECRET_KEY', 'Content-Type': 'application/json', }, body: JSON.stringify({ data: { name: 'Ada Lovelace', email: 'ada@example.com', message: 'Hello, I have a question...', } }) })
Decrement stock
Atomically decrements a numeric field — typically stock.
Call this from your payment webhook after a successful checkout.
Idempotent — passing the same order_id twice never double-decrements.
/v1/sites/:siteId/collections/:name/entries/:entryId/decrement Public| Field | Type | Required | Description |
|---|---|---|---|
order_id | string | ✓ | Your payment provider's charge ID. Used for idempotency. |
quantity | integer | How much to decrement. Default: 1. | |
field | string | Which field to decrement. Default: "stock". | |
order_data | object | Extra data stored on the order record. |
const charge = event.data.object; const res = await fetch( `https://api.foundiq.nl/v1/sites/$${SITE_ID}/collections/products/entries/$${productId}/decrement`, { method: 'POST', headers: { 'Authorization': `Bearer $${PUB_KEY}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ order_id: charge.id, quantity: 1, order_data: { customer_email: charge.billing_details.email, amount: charge.amount } }) } ); const { ok, remaining, code } = await res.json(); if (!ok && code === 'OUT_OF_STOCK') { /* refund */ }
{ "ok": true, "remaining": 7 }{ "ok": false, "code": "OUT_OF_STOCK", "remaining": 0 }List media files
Returns media files uploaded to a site. Filter by MIME type prefix.
/v1/sites/:siteId/media Public| Parameter | Description |
|---|---|
mime_type | Filter by MIME type. image/ matches all images. |
name | Filter by exact filename. |
limit, offset | Pagination. |
fetch('https://api.foundiq.nl/v1/sites/SITE_ID/media?mime_type=image/', { headers: { 'Authorization': 'Bearer pnd_pub_YOUR_KEY' } })
Single media file
Fetch a single media file by its filename.
/v1/sites/:siteId/media/:filename Publicfetch('https://api.foundiq.nl/v1/sites/SITE_ID/media/hero-banner.jpg', { headers: { 'Authorization': 'Bearer pnd_pub_YOUR_KEY' } })
Filtering & search
Filter entries by any field in their data object.
Multiple filters combine with AND.
// ?category=shoes&brand=nike → data.category = 'shoes' AND data.brand = 'nike'
GET /v1/sites/SITE_ID/collections/products/entries?category=shoes&brand=nike// Searches across ALL fields in data
GET /v1/sites/SITE_ID/collections/products/entries?search=running+shoePagination
All list endpoints return a meta object.
Use has_more to know when to stop.
async function fetchAll(siteId, collection, token) { const results = []; let offset = 0; const limit = 100; while (true) { const res = await fetch( `https://api.foundiq.nl/v1/sites/$${siteId}/collections/$${collection}/entries?limit=$${limit}&offset=$${offset}`, { headers: { 'Authorization': `Bearer $${token}` } } ); const { data, meta } = await res.json(); results.push(...data); if (!meta.has_more) break; offset += limit; } return results; }
Error reference
All errors return a consistent shape with a code you can match on.
{ "ok": false, "code": "NOT_FOUND", "message": "Collection \"products\" not found." }| HTTP | Code | Description |
|---|---|---|
| 401 | MISSING_TOKEN | No Authorization header. |
| 401 | INVALID_TOKEN | Token invalid or revoked. |
| 403 | FORBIDDEN | Token has no access to this site. |
| 403 | SECRET_KEY_REQUIRED | Endpoint requires a secret key. |
| 404 | NOT_FOUND | Collection, entry, or file not found. |
| 409 | OUT_OF_STOCK | Insufficient stock for decrement. |
| 200 | ALREADY_PROCESSED | order_id already decremented. |