Public API

Genjutsu Feed API

Fetch public 24-hour feed posts for your own website or app in real time.

GET /api/genjutsu-feed

Overview

This API returns the same public post feed style used in Genjutsu, including likes, comments, views, media, code, and readme-format metadata.

  • Public-only feed data
  • 24-hour retention window
  • Pagination via page and limit
  • Live polling support via since

Beginner Setup (Step by Step)

  1. Choose your base URL: https://genjutsu.xyz
  2. Build the endpoint URL: /api/genjutsu-feed?page=1&limit=10
  3. Send a GET request using browser fetch, backend code, or cURL.
  4. Check ok === true before reading posts.
  5. Render each post card using content, media_url, counts, and profile.
  6. For live updates, save meta.server_time and send it back as since. It is bucketed for shared cache efficiency.

If your site is static HTML/CSS/JS, you can use fetch directly in the browser. If your site is server-rendered, call this API on your backend and render from there.

Query Parameters

Param Type Default Description
page number 1 Page number (1-based).
limit number 10 Posts per page (max 50).
since ISO string null Only include posts newer than this timestamp (still constrained to 24h).

Validation rules:

  • page must be a positive integer.
  • limit must be a positive integer and is capped at 50.
  • since must be a valid ISO timestamp like 2026-04-28T10:30:00.000Z.
  • For best cache reuse, pass back meta.server_time instead of generating your own fresh timestamp.

Response Shape

{
  "ok": true,
  "meta": {
    "page": 1,
    "limit": 10,
    "has_more": true,
    "since": null,
    "window_hours": 24,
    "server_time": "2026-04-28T10:35:00.000Z",
    "poll_bucket_seconds": 60
  },
  "posts": [
    {
      "id": "uuid",
      "content": "post text",
      "code": "const x = 1;",
      "code_language": "javascript",
      "media_url": "https://...",
      "tags": ["react", "supabase"],
      "created_at": "2026-04-28T09:00:00.000Z",
      "edited_at": null,
      "user_id": "uuid",
      "is_readme": false,
      "views_count": 13,
      "likes_count": 5,
      "comments_count": 2,
      "profile": {
        "username": "ovi",
        "display_name": "Ovi",
        "avatar_url": "https://..."
      }
    }
  ]
}

Post Field Guide

Field Type How To Use
id string Unique key for rendering list items.
content string | null Main post text.
code string | null Code snippet body (if post contains code).
code_language string | null Syntax language label for code highlighting.
media_url string | null Image/media URL. Show only when present.
tags string[] Topic chips/labels.
created_at ISO string Post publish time.
edited_at ISO string | null Edit timestamp if post was updated.
is_readme boolean If true, render in README-style post layout.
views_count number Total views count.
likes_count number Total likes count.
comments_count number Total comments count.
profile.username string | null Author handle.
profile.display_name string | null Author display name.
profile.avatar_url string | null Author avatar image URL.

Errors & Status Codes

Status When Example
200 Successful request. { "ok": true, ... }
400 Invalid query parameter (for example invalid since format). { "ok": false, "error": "Invalid `since` parameter..." }
405 Method not allowed (only GET / OPTIONS allowed). { "ok": false, "error": "Method Not Allowed" }
500 Unexpected internal server error. { "ok": false, "error": "Unexpected server error" }
502 Upstream data fetch issue while building feed or counts. { "ok": false, "error": "Failed to fetch posts" }

All error responses follow a JSON shape with ok: false and an error message.

Caching Behavior

The public API is tuned for shared edge caching. Common feed pages can stay cached a bit longer, while since-based refreshes use shorter cache windows.

  • Reuse meta.server_time as your next since value.
  • Avoid generating a brand new timestamp on every poll.
  • Use small limits for live refreshes whenever possible.

Rate Limits & Fair Use

There is no dedicated API key or public per-user quota yet, but consumers should use reasonable polling and caching behavior.

  • Recommended polling interval for live updates: every 60 seconds (matching the server bucket).
  • Avoid high-frequency loops (for example sub-second polling).
  • Use since to fetch only new posts instead of repeatedly requesting large pages.
  • Respect caching headers returned by the endpoint when possible.

Future versions may enforce hard rate limits and API key-based access controls.

Quick Start

const res = await fetch("https://genjutsu.xyz/api/genjutsu-feed?limit=10&page=1");
const data = await res.json();
console.log(data.posts);

cURL Example

curl "https://genjutsu.xyz/api/genjutsu-feed?page=1&limit=10"

Frontend Example (Render Cards)

<div id="feed"></div>

<script>
  async function loadFeed() {
    const res = await fetch("https://genjutsu.xyz/api/genjutsu-feed?page=1&limit=10");
    const data = await res.json();
    if (!data.ok) return;

    const root = document.getElementById("feed");
    root.innerHTML = data.posts.map((post) => `
      <article>
        <h3>@${post.profile?.username || "unknown"}</h3>
        <p>${post.content || ""}</p>
        ${post.media_url ? `<img src="${post.media_url}" alt="post media" />` : ""}
        <small>❤ ${post.likes_count} · 💬 ${post.comments_count} · 👁 ${post.views_count}</small>
      </article>
    `).join("");
  }

  loadFeed();
</script>

Live Feed Polling Example

let since = new Date().toISOString();

async function pollFeed() {
  const url = new URL("https://genjutsu.xyz/api/genjutsu-feed");
  url.searchParams.set("since", since);
  url.searchParams.set("limit", "20");

  const res = await fetch(url.toString());
  const data = await res.json();
  if (!data.ok) return;

  // newest first from API: reverse if you append top-down
  for (const post of data.posts.reverse()) {
    console.log("new post", post.id, post.content);
  }

  since = data.meta.server_time;
}

setInterval(pollFeed, 10000);

Use since polling for efficient updates. It avoids re-fetching older posts each time.

Common Mistakes

  • Passing invalid since format (must be ISO timestamp).
  • Requesting limit over 50 and expecting more than the cap.
  • Assuming this endpoint returns data older than 24 hours.
  • Not checking ok before reading posts.
  • Rendering media_url without null checks.

Deployment Checklist

  • Deploy branch that includes /api/genjutsu-feed and /api-docs.
  • Open docs via http(s) URL, not file://.
  • Test from mobile and desktop.
  • Add caching/polling control on your client app.
  • Gracefully handle non-200 responses.

Try It

Run a request to preview JSON response here.