Developer API

Build your own frontend, powered by Fanvaiy

Every Fanvaiy site has a free, open, read-only API. Pass your siteId and pull your published posts, categories and branding as JSON — then render them however you like, in React, Vue, Svelte, plain HTML, or anything that can fetch a URL. No API key, no SDK, no sign-up.

Open & keyless

It serves public content, so there's nothing to authenticate. Call it from a browser or a server — CORS is enabled for any origin.

Read-only & safe

Only published, free posts and public site metadata are returned. Billing, drafts, paid articles and private fields are never exposed.

Fast & cached

Every response carries Cache-Control and an ETag, so reads are served from cache and conditional requests return 304.

Quickstart

  1. 1

    Find your siteId

    Open your Fanvaiy dashboard — the site id is in your site settings. You can also use your subdomain or custom domain in its place.

  2. 2

    Call the API

    The base URL is:

    https://fanvaiy.com/api/public/v1

    Fetch your latest posts:

    curl "https://fanvaiy.com/api/public/v1/sites/YOUR_SITE_ID/posts?limit=10"
  3. 3

    Render it

    A minimal example in JavaScript:

    const SITE = 'YOUR_SITE_ID';
    const res  = await fetch(`https://fanvaiy.com/api/public/v1/sites/${SITE}/posts`);
    const { data, meta } = await res.json();
    
    data.forEach(post => {
      console.log(post.title, '→', post.url);
    });

API reference

All endpoints are GET, return JSON, and accept your site id, subdomain, or custom domain as {siteId}.

GET /api/public/v1/sites/{siteId}

Site profile — name, branding (logo, favicon, colour, theme), social handles, contact, legal pages, SEO metadata, and the list of categories. The one call to bootstrap a frontend.

Example response

{
  "data": {
    "id": "d39dc768-…",
    "name": "My Magazine",
    "slogan": "Stories worth your time",
    "color": "#111111",
    "logo": "https://…/logo.png",
    "theme": "news",
    "lang": "en",
    "url": "https://mymag.com",
    "social": { "twitter": "mymag", "instagram": "mymag", "facebook": null },
    "seo": { "ogTitle": "My Magazine", "ogImage": "https://…" },
    "categories": [ { "id": "…", "name": "World", "slug": "world" } ]
  }
}
GET /api/public/v1/sites/{siteId}/categories

Just the site's categories, ordered the way they appear on the site.

Example response

{
  "data": [
    { "id": "…", "name": "World", "slug": "world", "isPromoted": true },
    { "id": "…", "name": "Sports", "slug": "sports", "isPromoted": false }
  ]
}
GET /api/public/v1/sites/{siteId}/posts

Published posts, newest first (paid posts excluded). Returns post summaries without the article body.

Query parameters

category string Filter by category slug.
featured boolean Set to true to return only featured posts.
search string Full-text match on title and summary.
page integer Page number (default 1).
limit integer Per page, 1–50 (default 20).

Example response

{
  "data": [
    {
      "id": "581aac37-…",
      "title": "Crafting a Gripping Horror Story",
      "summary": "Learn how to write a horror story that…",
      "image": "https://…/cover.jpg",
      "isFeatured": false,
      "publishedAt": "2026-05-10T08:30:00+00:00",
      "likes": 12,
      "tags": ["writing", "horror"],
      "url": "https://mymag.com/story/581aac37-…",
      "category": { "name": "Writing", "slug": "writing" },
      "author": { "name": "Aisha", "profilePicture": "https://…" }
    }
  ],
  "meta": { "page": 1, "limit": 20, "total": 134, "totalPages": 7 }
}
GET /api/public/v1/sites/{siteId}/posts/{id}

A single post including the full HTML body, author, category, and audio (if the article has a narration).

Example response

{
  "data": {
    "id": "581aac37-…",
    "title": "Crafting a Gripping Horror Story",
    "summary": "Learn how to write a horror story that…",
    "body": "<p>The first rule of horror is…</p>",
    "image": "https://…/cover.jpg",
    "publishedAt": "2026-05-10T08:30:00+00:00",
    "url": "https://mymag.com/story/581aac37-…",
    "category": { "name": "Writing", "slug": "writing" },
    "author": { "name": "Aisha", "profilePicture": "https://…" },
    "audio": { "url": "https://…/narration.mp3", "duration": 412 }
  }
}

Good to know

Paid posts are private

Posts behind a paywall are never returned by this API — neither in lists nor by direct id. Only free, published posts are public.

Caching

Responses are cacheable and carry an ETag. Send it back as If-None-Match to get a fast 304 Not Modified when nothing changed.

Rate limits

There's a generous safety limit per IP. For high-traffic frontends, cache responses on your side — the data changes infrequently.

Errors

Unknown or blocked sites and missing posts return 404 with a JSON body. All responses are JSON.

Don't have a site yet?

Create a Fanvaiy blog in minutes, then build any frontend you like on top of it.

Create a blog