Documentation
Content API
On this page
The Content API delivers published content to the world and can be accessed in a read-only manner by any client to render in a website, app, or other embedded media.
URL
https://www.wisp.blog/api/v1/{blogId}
All read request will be made to your blog-specific endpoint. To get your blog ID and blog's endpoint, log in to wisp and visit the Setup Page.
Working Example
Try out a live example below.
curl https://www.wisp.blog/api/v1/cluqyx1rl0000l5ds3f0vkfer/posts
Endpoints
The Content API provides access to Posts & Tags. All endpoints return JSON and are considered stable.
Verb | Path | Method |
---|---|---|
GET | /posts/ | List blog posts |
GET | /posts/{slug} | Get specific blog post by slug |
GET | /posts/{slug}/related | Get related blog posts by slug |
GET | /tags | List tags for blog posts |
GET | /contents/{content-type-slug} | List all content of a content type |
GET | /contents/{content-type-slug}/{content-slug} | Get specific content of a type by its slug |
Blog Posts
Blog post are the primary content on a wisp blog.
List Blog Posts
GET https://www.wisp.blog/api/v1/{blogId}/posts
Using the posts endpoint it is possible to get lists of posts filtered by various criteria. Blog posts are returned in reverse chronological order by published date.
Available Filters
Query | Remarks | Default |
---|---|---|
limit | Number of post to fetch, accepts numbers & 'all' to fetch all posts | 20 |
page | Page number of blog post to fetch, starts from 1 | 1 |
tag | Return only blog posts with the at least one of the tags, accepts multiple value | null |
query | Perform a full text search for blog posts (detailed in separate section) | null |
Sample Requests
List all latest blog post using default page size of 20:
GET https://www.wisp.blog/api/v1/{blogId}/posts
List the second page of blog post, with page size of 10, filtered by announcement
tag:
GET https://www.wisp.blog/api/v1/{blogId}/posts?page=2&limit=10&tag=announcement
List all latest blog post with either javascript
or nextjs
tag:
GET https://www.wisp.blog/api/v1/{blogId}/posts?tag=javascript&tag=nextjs
Sample Response
{
"posts": [
{
"id": "clv9qc8h000032ljsujj254rq",
"title": "When to Say No to Next.js: The Guide for Minimalist Web Developers",
"image": "https://imagedelivery.net/lLmNeOP7HXG0OqaG97wimw/cluqyx1rl0000l5ds3f0vkfer/68b4d481-56c2-43ee-85d1-77d9c98bee3e.png/public",
"description": "The appeal of Next.js is obvious - built-in server-side rendering, static site generation,file-based routing, and API routes promise a smooth, full-stack development experience. But at what cost?",
"slug": "when-to-say-no-to-nextjs-the-guide-for-minimalist-web-developers",
"authorId": "cluqywzs7000021a9m3aueeiq",
"teamId": "cluqyx1rl0000l5ds3f0vkfer",
"createdAt": "2024-04-21T16:15:34.885Z",
"updatedAt": "2024-04-21T16:15:34.885Z",
"published": true,
"author": {
"name": "John Doe",
"image": "<URL_TO_IMAGE>"
},
"tags": [
{
"id": "clv9qc1ik00012ljs7wb9c85z",
"name": "nextjs"
}
]
},
{
"id": "clv9mc47c0001scsnebq69abr",
"title": "New Slash Command and Image Upload Upgrades",
"image": "https://imagedelivery.net/lLmNeOP7HXG0OqaG97wimw/cluqyx1rl0000l5ds3f0vkfer/87dfe381-d340-46c8-b4c6-a9517c2ac143.png/public",
"description": "We're excited to announce some slick new upgrades to the wisp editor experience! First up, we've added a handy slash command that gives you a Notion-like experience for quickly formatting content.",
"slug": "new-slash-command-and-image-upload-upgrades",
"authorId": "cluqywzs7000021a9m3aueeiq",
"teamId": "cluqyx1rl0000l5ds3f0vkfer",
"createdAt": "2024-04-21T14:23:30.887Z",
"updatedAt": "2024-04-21T14:23:30.887Z",
"published": true,
"author": {
"name": "John Doe",
"image": "<URL_TO_IMAGE>"
},
"tags": [
{
"id": "clv5860ec0001qcy3k91unqvy",
"name": "announcement"
}
]
}
],
"pagination": {
"page": 1,
"limit": 20,
"totalPages": 1,
"totalPosts": 32,
"nextPage": 1,
"prevPage": null
}
}
Get Blog Post by Slug
GET https://www.wisp.blog/api/v1/{blogId}/posts/{slug}
Using the posts endpoint, you can get a single blog post by it's slug. This endpoint gives the full content of the blog post in HTML format, unlike the listing endpoint which only return a subset of the blog post object.
Sample Request
Get blog post with the slug introducing-wisp
:
GET https://www.wisp.blog/api/v1/{blogId}/posts/introducing-wisp
Sample Response
{
"post": {
"id": "cluqzhjn70005ihniuhbpkxa8",
"title": "Introducing wisp: A Headless CMS for Indie Hackers & Startups",
"image": "https://imagedelivery.net/lLmNeOP7HXG0OqaG97wimw/cluqyx1rl0000l5ds3f0vkfer/a8ba0df1-942a-42d0-9150-976966459c4f.png/public",
"content": "<img src=\"https://imagedelivery.net/lLmNeOP7HXG0OqaG97wimw/cluqyx1rl0000l5ds3f0vkfer/a8ba0df1-942a-42d0-9150-976966459c4f.png/public\"><h2>Blogs on Next.js SUCKS!!!</h2><p>Let's be real here, managing a blog on your Next.js website can be a total pain in the ass, especially if you're an indie hacker or a bootstrapped startup. I've been there, done that, and I've got the battle scars to prove it.</p><p>Working with MDX files was supposed to be a breeze, but it quickly turned into a nightmare. Generating sitemaps, creating blog lists, and handling image uploads and hosting became tasks that I dreaded.</p>(truncated)",
"description": "Managing a blog on your Next.js website sucks. I've been there, done that, and I've got the battle scars to prove it. That's why I set out to build wisp. A headless CMS with kickass editor, image CDN and everything you need in a blog.",
"slug": "introducing-wisp-a-headless-cms-for-indie-hackers-and-startups",
"authorId": "cluqywzs7000021a9m3aueeiq",
"teamId": "cluqyx1rl0000l5ds3f0vkfer",
"createdAt": "2024-04-08T13:24:01.843Z",
"updatedAt": "2024-04-18T12:35:49.778Z",
"metadata": { "foo": "bar" },
"published": true,
"author": {
"name": "John Doe",
"image": "<URL_TO_IMAGE>"
},
"tags": [
{
"id": "clv5860ec0001qcy3k91unqvy",
"name": "announcement"
}
]
}
}
Full Text Search on Blog Posts
GET https://www.wisp.blog/api/v1/{blogId}/posts/?query={search_query}
Besides simple filtering, you can also perform full text search on your blog posts. Simply pass in the search query into the query
parameter and you will receive list of blog posts that fulfils the search criteria, ranked by relevancy.
Sample Request
Finding blog post mentioning google ads
:
GET https://www.wisp.blog/api/v1/{blogId}/posts/?query=google%20ads
Sample Response
{
"posts": [
{
"id": "clx8isasu000cpjg9qegp2mr9",
"title": "Protect Your Ad Spend: Exclude These Countries on Google Ads",
"image": "https://imagedelivery.net/lLmNeOP7HXG0OqaG97wimw/cluqyx1rl0000l5ds3f0vkfer/ae76d368-cd80-44cf-86ab-81b4d85ba278.png/public",
"description": "Stop throwing money away on useless traffic from India, Pakistan, and more. This guide reveals how to exclude problematic countries and laser-focus your Google Ads for maximum conversions.",
"slug": "protect-your-ad-spend-exclude-these-countries-on-google-ads",
"authorId": "cluqywzs7000021a9m3aueeiq",
"teamId": "cluqyx1rl0000l5ds3f0vkfer",
"createdAt": "2024-06-10T05:15:45.966Z",
"updatedAt": "2024-06-10T05:15:45.966Z",
"publishedAt": "2024-06-10T05:15:43.543Z",
"tags": [
{
"id": "clw0ctnix00036zwy46r5hgzq",
"name": "content-distribution"
},
... more tags
]
},
... more posts
],
"pagination": {
"page": 1,
"limit": 20,
"totalPages": 1,
"totalPosts": 16,
"nextPage": null,
"prevPage": null
}
}
Get Related Blog Posts
GET https://www.wisp.blog/api/v1/{blogId}/posts/{slug}/related
This endpoint fetches blog posts that are related to a specific blog post. This is often used to build a related post or read next section of your blog post.
wisp cms uses AI to determine the semantic similarity between blog posts to suggest blog posts that are related. You do not need to manually link articles that are related together.
Available Filters
Query | Remarks | Default |
---|---|---|
limit | Number of post to fetch, with maximum of 10 | 10 |
Sample Request
Get 2 blog posts that are most similar to the post with the slug introducing-wisp
:
GET https://www.wisp.blog/api/v1/{blogId}/posts/introducing-wisp/related?limit=2
{
"posts": [
{
"id": "clvm0w1x5001gnroh68ccuy3m",
"title": "The Magic of Spontaneity: Unplanned Adventures",
"description": "Embracing spontaneity leads to the most cherished travel memories. Wandering without an agenda opens up unexpected adventures - stumbling upon hidden waterfalls, ending up in vibrant towns. Saying \"yes\" to the unplanned is true magic.",
"image": "https://imagedelivery.net/lLmNeOP7HXG0OqaG97wimw/clvlugru90000o4g8ahxp069s/db4ef086-b017-43f9-b1b8-09f43fd934e3.png/public",
"slug": "the-magic-of-spontaneity-unplanned-adventures",
"authorId": "clur03y7n00006jrfzjj264vv",
"teamId": "clur04ass00056jrfdvxn6dg1",
"createdAt": "2024-05-05T00:00:00.000Z",
"updatedAt": "2024-07-04T06:13:34.626Z",
"publishedAt": "2024-05-07T16:00:00.000Z"
},
{
"id": "clvm04tgm000dnrohmrx32cbs",
"title": "Trekking the Himalayas: Pushing My Limits in Nepal",
"description": "Trekking through the awe-inspiring Himalayas in Nepal was an experience that pushed me to my absolute limits, both physically and mentally. As I navigated treacherous high-altitude passes, glaciers, and sub-freezing temperatures, I was forced to confront my fears, self-doubts, and vulnerabilities head-on. But in doing so, I discovered reserves of strength and resilience I never knew I possessed. More than just a grueling physical journey, this trek was a transformative spiritual and emotional awakening. It reminded me that growth often lies on the other side of our greatest challenges, and that by embracing the unknown, we open ourselves up to experiences that can change us in profound and lasting ways.",
"image": "https://imagedelivery.net/lLmNeOP7HXG0OqaG97wimw/clvlugru90000o4g8ahxp069s/3307f9f2-0363-487c-9e63-f2444697c8ae.png/public",
"slug": "trekking-the-himalayas-pushing-my-limits-in-nepal",
"authorId": "clur03y7n00006jrfzjj264vv",
"teamId": "clur04ass00056jrfdvxn6dg1",
"createdAt": "2024-05-05T00:00:00.000Z",
"updatedAt": "2024-07-04T06:41:15.866Z",
"publishedAt": "2024-05-05T00:00:00.000Z"
}
]
}
Get Contextual CTA for Blog Post
GET https://www.wisp.blog/api/v1/{blogId}/posts/{slug}/ctas
This endpoint fetches contextual Call-To-Actions (CCTAs) that are related to a specific blog post. This is used to determine which CTA to serve on the frontend for a specific blog post.
Available Filters
Query | Remarks | Default |
---|---|---|
limit | Number of ctas to fetch, with maximum of 10 | 10 |
The endpoint will return the following fields which can be used to power the frontend:
Key | Remarks |
---|---|
slug | Alpha-numeric string that can be used as the key to select the CTA to render on the frontend. |
title | Title of the CTA. Can be used for annotation only or injected into the frontend directly. |
description | Description of the CTA. Can be used for annotation only or injected into the frontend directly. (optional) |
distance | Cosine distance of the blog post embedding and CTA embedding. Can be used serve CTA using a weighted random algorithm. |
Sample Request
Get a single CTA suggestion for the post with the slug introducing-wisp
:
GET https://www.wisp.blog/api/v1/{blogId}/posts/introducing-wisp/ctas
{
"ctas": [
{
"id": "t41q909hjjzmh35vzrucsw9i",
"title": "Choosing a CMS?",
"description": "Wisp is the most delightful and intuitive way to manage content on your website. Integrate with any existing website within hours!",
"slug": "choosing-cms",
"teamId": "clur04ass00056jrfdvxn6dg1",
"createdAt": "2024-08-14T08:40:31.938Z",
"updatedAt": "2024-08-17T14:01:04.945Z",
"distance": 0.5148561988448501
}
]
}
Tags
Tags are labels you can give to blog posts on wisp. You can use tags to categorize and filter blog post using the filter
query on the list blog post endpoint.
List Tags
GET https://www.wisp.blog/api/v1/{blogId}/tags
Using the tags endpoint, you can list all the tags available on your site. This can be useful if you are creating a sitemap or a taxonomy list for your user.
Available Filters
Query | Remarks | Default |
---|---|---|
limit | Number of tags to fetch, accepts numbers & 'all' to fetch all tags | 20 |
page | Page number of tags to fetch, starts from 1 | 1 |
Sample Request
Get all tags on the blog:
GET https://www.wisp.blog/api/v1/{blogId}/tags
Sample Response
{
"tags": [
{
"id": "clv5860ec0001qcy3k91unqvy",
"name": "announcement",
"description": null,
"teamId": "cluqyx1rl0000l5ds3f0vkfer"
},
{
"id": "clv588snu000fqcy3zyusi5ol",
"name": "blogging",
"description": null,
"teamId": "cluqyx1rl0000l5ds3f0vkfer"
},
{
"id": "clv586u4y0006qcy3k30yli5z",
"name": "growth",
"description": null,
"teamId": "cluqyx1rl0000l5ds3f0vkfer"
}
],
"pagination": {
"page": 1,
"totalPages": 1,
"totalTags": 6,
"nextPage": null,
"prevPage": null
}
}
Custom Content Types
Besides blog posts, wisp supports custom content type with their own schemas.
List Contents
GET https://www.wisp.blog/api/v1/{blogId}/contents/{content-type-slug}
Using the content listing endpoint it is possible to get a lists of content of a specific content type. Contents are returned in reverse chronological order by published date.
Available Filters
Query | Remarks | Default |
---|---|---|
limit | Number of content to fetch, accepts numbers & 'all' to fetch all posts | 20 |
page | Page number of content to fetch, starts from 1 | 1 |
Sample Requests
List all latest Product Reviews using default page size of 20:
GET https://www.wisp.blog/api/v1/{blogId}/content/product-review
Sample Response
{
"contents": [
{
"id": "cm3wqv1cz0003ogrcyi2ychbc",
"slug": "wisp-cms",
"publishedAt": "2024-11-25T08:06:30.968Z",
"createdAt": "2024-11-25T08:06:32.867Z",
"updatedAt": "2024-11-25T08:06:32.867Z",
"content": {
"name": "Wisp CMS",
"rating": 5,
"review": "<h2>Introduction</h2><p>blah blah blah..</p><p>...</p><p>..</p><h2>Conclusion</h2><p>Great product, must try</p>"
},
"author": {
"name": "Raymond Yeh",
"image": "https://example.com/images/avatar.png"
}
}
],
"contentType": {
"name": "Product Review",
"slug": "product-review",
"schema": {
"version": "0.0.1",
"fields": [
{
"label": "Product Name",
"name": "name",
"type": "text",
"required": true
},
{
"label": "Rating",
"name": "rating",
"type": "number",
"required": true
},
{
"label": "Review",
"name": "review",
"type": "rich_text"
}
]
}
},
"pagination": {
"page": 1,
"limit": 20,
"totalPages": 1,
"totalContents": 5,
"nextPage": null,
"prevPage": null
}
}
Get Content by Slug
GET https://www.wisp.blog/api/v1/{blogId}/contents/{content-type-slug}/{content-item-slug}
Using the content endpoint, you can get a single content post by it's slug.
Sample Request
Get product review with the slug wisp-cms
:
GET https://www.wisp.blog/api/v1/{blogId}/content/product-review/wisp-cms
Sample Response
{
"content": {
"id": "cm3wqv1cz0003ogrcyi2ychbc",
"slug": "wisp-cms",
"publishedAt": "2024-11-25T08:06:30.968Z",
"createdAt": "2024-11-25T08:06:32.867Z",
"updatedAt": "2024-11-25T08:06:32.867Z",
"content": {
"name": "Wisp CMS",
"rating": 5,
"review": "<h2>Introduction</h2><p>blah blah blah..</p><p>...</p><p>..</p><h2>Conclusion</h2><p>Great product, must try</p>"
},
"author": {
"id": "cluqywzs7000021a9m3aueeiq",
"name": "Raymond Yeh",
"image": "https://example.com/images/avatar.png"
}
},
"contentType": {
"name": "Product Review",
"slug": "product-review",
"schema": {
"fields": [
{
"name": "name",
"type": "text",
"label": "Product Name",
"required": true
},
{
"name": "rating",
"type": "number",
"label": "Rating",
"required": true
},
{
"name": "review",
"type": "rich_text",
"label": "Review"
}
],
"version": "0.0.1"
}
}
}