Content API

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.

VerbPathMethod
GET/posts/List blog posts
GET/posts/{slug}Get specific blog post by slug
GET/posts/{slug}/relatedGet related blog posts by slug
GET/tagsList 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

QueryRemarksDefault
limitNumber of post to fetch, accepts numbers & 'all' to fetch all posts20
pagePage number of blog post to fetch, starts from 11
tagReturn only blog posts with the at least one of the tags, accepts multiple valuenull
queryPerform 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 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

QueryRemarksDefault
limitNumber of post to fetch, with maximum of 1010

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

QueryRemarksDefault
limitNumber of ctas to fetch, with maximum of 1010

The endpoint will return the following fields which can be used to power the frontend:

KeyRemarks
slugAlpha-numeric string that can be used as the key to select the CTA to render on the frontend.
titleTitle of the CTA. Can be used for annotation only or injected into the frontend directly.
descriptionDescription of the CTA. Can be used for annotation only or injected into the frontend directly. (optional)
distanceCosine 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

QueryRemarksDefault
limitNumber of tags to fetch, accepts numbers & 'all' to fetch all tags20
pagePage number of tags to fetch, starts from 11

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

QueryRemarksDefault
limitNumber of content to fetch, accepts numbers & 'all' to fetch all posts20
pagePage number of content to fetch, starts from 11

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"
    }
  }
}