Publish content
Create a blog, publication, or project in Zerko CMS and set it to Published.
Create and publish once, then deliver blogs, publications, projects, team profiles, media, settings, and forms to any website.
const response = await fetch(
"https://cms.zerko.app/api/cms/public/blogs",
{
headers: {
"X-Zerko-Api-Key": process.env.ZERKO_CMS_API_KEY
}
}
);
const { data: posts } = await response.json();Publish your first website-powered page in a few minutes.
Create a blog, publication, or project in Zerko CMS and set it to Published.
Open Site settings → API keys. Name the key after the website that will use it.
Store the key safely on your website server and request the content you need.
Use a server-only environment variable. Never commit an API key to Git or place it in public browser code.
Every Delivery API request belongs to one Zerko workspace.
Send your key in the X-Zerko-Api-Key header. The key identifies the workspace automatically, so no tenant or company ID is needed in the URL.
ZERKO_CMS_URL=https://cms.zerko.app
ZERKO_CMS_API_KEY=zrk_live_your_key_hereX-Zerko-Api-Key: zrk_live_your_key_hereKeys are shown once when created. If one is exposed, revoke it in Site settings and create a replacement.
Always call cms.zerko.app. It is the protected CMS gateway to Zerko's shared platform; websites never need the main operations API address or its private service credential.
All paths below follow the public Delivery API base URL.
https://cms.zerko.app/api/cms/public/bootstrapLoad site settings, recent content, team profiles, and active forms together.
/contentList all published content, with optional type and pagination filters.
/content/{slug}Load one published item using its public slug.
/blogsList published blog posts.
/publicationsList publications and their downloadable documents.
/projectsList published projects.
/settingsLoad site identity, SEO, social links, and appearance settings.
/teamList company profiles marked as published.
/mediaList uploaded public media with pagination.
/forms/{slug}Load an active form and its fields.
/forms/{slug}/submissionsSend a completed form to the CMS inbox.
List endpoints accept page and limit. The general content endpoint also accepts type=blog, type=publication, or type=project.
GET /content?type=blog&page=1&limit=12Page numbers start at 1. The API accepts between 1 and 100 items per page and safely adjusts values outside that range.
{
"data": [],
"meta": {
"page": 1,
"limit": 12,
"total": 0,
"pages": 0
}
}A stable shape shared by blogs, publications, and projects.
$id, type, title, slugexcerpt, content, tagscoverImage, featuredmetadata, publishedAt, updatedAt{
"$id": "article_id",
"type": "blog",
"title": "Building better customer experiences",
"slug": "building-better-customer-experiences",
"excerpt": "A practical guide for growing teams.",
"content": "<p>Rich, sanitized HTML content...</p>",
"coverImage": "https://.../hero.jpg",
"featured": true,
"tags": ["customer experience", "growth"],
"metadata": {},
"downloads": [],
"publishedAt": "2026-08-09T10:00:00Z",
"updatedAt": "2026-08-09T10:00:00Z"
}Publication items include a downloads array with document name, file type, size, and direct download URL.
The team endpoint returns only profiles marked as published, already sorted by display position.
Use one response for brand identity, SEO details, social profiles, colours, logo, and browser icon.
Turn the formatted article from Zerko into a polished page on your website.
The content field contains HTML created in the Zerko editor. Zerko cleans this HTML before saving it while preserving headings, paragraphs, lists, links, quotes, images, code blocks, highlights, and text alignment.
The saved article is cleaned by the CMS. Do not mix visitor form answers or other untrusted text into the HTML before rendering it.
type PageProps = { params: Promise<{ slug: string }> };
export default async function ArticlePage({ params }: PageProps) {
const { slug } = await params;
const response = await fetch(
`${process.env.ZERKO_CMS_URL}/api/cms/public/content/${slug}`,
{
headers: { "X-Zerko-Api-Key": process.env.ZERKO_CMS_API_KEY! },
next: { revalidate: 60 }
}
);
if (response.status === 404) return <p>Article not found.</p>;
if (!response.ok) throw new Error("Could not load article");
const { data: article } = await response.json();
return (
<article>
<h1>{article.title}</h1>
<div
className="article-content"
dangerouslySetInnerHTML={{ __html: article.content }}
/>
</article>
);
}.article-content { color: #27272a; font-size: 1.08rem; line-height: 1.8; }
.article-content h2 { margin: 2.5rem 0 1rem; font-size: 1.8rem; }
.article-content h3 { margin: 2rem 0 .75rem; font-size: 1.35rem; }
.article-content p, .article-content ul, .article-content ol { margin: 1rem 0; }
.article-content blockquote { border-left: 3px solid #c8a45d; padding-left: 1rem; }
.article-content img { display: block; max-width: 100%; height: auto; border-radius: 1rem; }
.article-content a { color: inherit; text-decoration: underline; }
.article-content pre { overflow-x: auto; padding: 1rem; border-radius: .75rem; }Use more than articles: power your brand, people, downloads, and media from one place.
GET /bootstrap returns four top-level values. It is ideal for a homepage or initial server render.
{
"site": {
"siteName": "Zerko",
"tagline": "Build better, faster.",
"logoUrl": "https://.../logo.png",
"faviconUrl": "https://.../favicon.png",
"primaryColor": "#C8A45D",
"locale": "en",
"timezone": "Africa/Dar_es_Salaam",
"seoTitle": "Zerko",
"seoDescription": "...",
"imageOptimizationEnabled": true,
"imageQuality": 85,
"imageMaxWidth": 2560,
"social": { "linkedin": "https://linkedin.com/company/zerko" }
},
"content": [],
"team": [],
"forms": []
}Read identity, domain, logo, browser icon, brand colour, language, timezone, search description, and social profiles from /settings.
Read published profiles from /team. Each profile can include name, role, biography, uploaded photo, contact details, social links, and display position.
Read uploaded public files from /media. Each item includes its name, type, size, public view URL, download URL, alt text, and folder.
Every publication can expose a downloads list with document name, file type, byte size, and public URL.
Workspace admins can control automatic image optimization in Site settings → Images. Zerko uses Sharp before storage, preserves the original format and proportions, never enlarges small images, and leaves documents and animated GIFs unchanged.
const { data: publications } = await cms.publications();
return publications.map((publication) => (
<article key={publication.$id}>
<h2>{publication.title}</h2>
{publication.downloads.map((file) => (
<a key={file.id} href={file.url} download>
Download {file.name} ({file.mimeType})
</a>
))}
</article>
));Use the API directly or copy the typed Zerko client into your project.
async function getPosts() {
const response = await fetch(
`${process.env.ZERKO_CMS_URL}/api/cms/public/blogs`,
{
headers: {
"X-Zerko-Api-Key": process.env.ZERKO_CMS_API_KEY!
},
next: { revalidate: 60 }
}
);
if (!response.ok) throw new Error("Could not load posts");
return response.json();
}
export default async function BlogPage() {
const { data: posts } = await getPosts();
return posts.map((post) => (
<a key={post.$id} href={`/blog/${post.slug}`}>
<h2>{post.title}</h2>
<p>{post.excerpt}</p>
</a>
));
}const response = await fetch(
`${process.env.ZERKO_CMS_URL}/api/cms/public/content/${slug}`,
{ headers: { "X-Zerko-Api-Key": process.env.ZERKO_CMS_API_KEY } }
);
const { data: article } = await response.json();const response = await fetch(
`${process.env.ZERKO_CMS_URL}/api/cms/public/bootstrap`,
{ headers: { "X-Zerko-Api-Key": process.env.ZERKO_CMS_API_KEY } }
);
const website = await response.json();
// website.site, website.content, website.team, website.formsFor a small site, call the API from your own server endpoint and send only the published response to the browser.
const response = await fetch(
"https://cms.zerko.app/api/cms/public/projects?page=1&limit=12",
{ headers: { "X-Zerko-Api-Key": process.env.ZERKO_CMS_API_KEY } }
);
if (!response.ok) throw new Error(`Zerko CMS returned ${response.status}`);
const { data: projects, meta } = await response.json();Published pages: cache read requests for 30–60 seconds for fast pages and timely updates.
Preview or urgent changes: use cache: "no-store" temporarily.
Form submissions: never cache a POST request.
Images and files: use the public URL returned by Zerko; do not rebuild storage URLs yourself.
Display a CMS-managed form and send responses back to the workspace inbox.
Build questions visually in Zerko CMS, then load the active definition with GET /forms/{slug}. Render fields in the returned order and submit answers using their stable name values.
{
"data": {
"$id": "form_id",
"name": "Project enquiry",
"slug": "project-enquiry",
"successMessage": "Thanks — we received your request.",
"active": true,
"fields": [
{
"id": "full_name",
"name": "full_name",
"label": "Full name",
"type": "text",
"required": true,
"placeholder": "Amina Said",
"helpText": "",
"options": []
},
{
"id": "service",
"name": "service",
"label": "What do you need?",
"type": "select",
"required": true,
"placeholder": "",
"helpText": "Choose the closest option.",
"options": ["Website", "Branding", "Consulting"]
}
]
}
}{
"data": {
"name": "Amina Said",
"email": "[email protected]",
"message": "I would like to discuss a project."
},
"sourceUrl": "https://example.com/contact"
}{
"submissionId": "submission_id",
"message": "Thank you. We will be in touch shortly."
}Supported field types are short answer, email, phone, long answer, choice list, checkbox, date, and number. Zerko rejects missing required answers, unknown fields, invalid email addresses, and choices that are not in the saved list.
Every valid response appears under Contact forms → Recent responses and the form's Responses view. Email alerts use the workspace's connected SMTP account, with Zerko's backend SMTP as fallback. Notification recipient addresses remain private and are never returned by the public form endpoint.
Predictable responses make website integrations easier to operate.
400Check filters, form fields, or the submitted JSON body.
401Add a valid, active API key to the request header.
404The item may not exist, may still be a draft, or the form may be inactive.
5xxKeep the last cached page visible and retry the request after a short delay.
Create the replacement key, deploy it to the website, confirm requests work, then revoke the old key.
Getting 401? Confirm the header name is exactly X-Zerko-Api-Key, the key begins with zrk_live_, and it has not been revoked or expired.
Getting 404 for an article? Confirm its slug and make sure the article is Published, not Draft, Scheduled, In review, or Archived.
Images do not appear? Use coverImage, photoUrl, or the media item's returned url exactly as supplied.
Updates seem delayed? Clear the website cache or wait for its configured revalidation time.