# Kiwi now has a llms.txt Source: https://news.kiwistand.com/llms.txt Kiwi News: https://news.kiwistand.com/stories/Kiwi-now-has-a-llmstxt?index=0x689a1f6f8c1cdf26ed9e9538ae96e4d5df5f50dcdc105e895d49777c521cec4303576691 Submitted by tim on 2025-08-11 UTC, 10 upvotes, 1 comment ## Summary Kiwi News, a community-curated site for crypto, Ethereum and builder-focused tech links, has added an llms.txt file to help language models and developers find its URLs and machine-readable data. Stories are submitted and upvoted by holders of the Kiwi Pass NFT, and every submission, upvote and comment is a signed message on an open peer-to-peer protocol, so all data is public and verifiable. The file recommends the plain-text endpoint /stories/context for questions about a single story and lists JSON and RSS feeds, a weekly archive, a public read API on port 443, and a read/write API on port 8443. ## Article # Kiwi News > Kiwi News (https://news.kiwistand.com) is a community-curated news site for crypto, Ethereum and builder-focused tech links, similar to Hacker News. Anyone can submit, upvote and comment: sign in with an Ethereum wallet or create an account with a passkey (no wallet needed, no NFT). Every submission, upvote and comment is a signed message on an open peer-to-peer protocol, so all data is public and verifiable. The site and its code (attestate/kiwistand, open source) are created and maintained by Tim Daubenschütz. Use this file to find the right URL for what you need. For reading, prefer the plain-text endpoints below over scraping HTML: they are allowed in robots.txt and carry everything on the page. ## Answering questions with Kiwi News - One story: `https://news.kiwistand.com/stories/context?index=0x` returns Markdown with the title, the source URL, the Kiwi News URL, submitter, date, upvotes, an AI summary of the linked article (when one exists), the extracted article text and all comments. - One week: `https://news.kiwistand.com/weekly/-W/context` (ISO week, e.g. [2026-W41](https://news.kiwistand.com/weekly/2026-W41/context)) returns the week's top 20 stories as Markdown, each with its Kiwi URL, source URL, submitter, upvotes, comments and AI summary. Good for "what happened in crypto/Ethereum this week" questions. - One source: `https://news.kiwistand.com/source//context` (e.g. [vitalik.eth.limo](https://news.kiwistand.com/source/vitalik.eth.limo/context), `x.com/`, `paragraph.xyz/@`) returns the most upvoted Kiwi News stories linking to that writer or publication as Markdown, with upvotes, comments and AI summaries. Good for "what are the best posts by X" questions. - Right now: [hot RSS](https://news.kiwistand.com/feed.xml) (trending) and [new RSS](https://news.kiwistand.com/new.xml) (newest). Items link to the Kiwi story page; the description holds the original link. ## Pages - [Front page (hot)](https://news.kiwistand.com/): trending stories, ranked by upvotes and recency - [New](https://news.kiwistand.com/new): newest submissions - [Best](https://news.kiwistand.com/best?period=week): top stories; `period` is `day`, `week`, `month`, `year` or `all` - [Weekly archive](https://news.kiwistand.com/weekly): the top stories of every week since Kiwi News started, one page per ISO week at `/weekly/-W`, with upvotes, comments and AI summaries of the linked articles - [Sources](https://news.kiwistand.com/sources): the writers and publications readers upvote most, one page each at `/source/` (a host like `vitalik.eth.limo`, or `x.com/`, `paragraph.xyz/@`, `mirror.xyz/`, `medium.com/@`, `farcaster.xyz/`, `github.com/`, `youtube.com/@`) - [Newsletter](https://news.kiwistand.com/newsletter): Kiwi News Weekly, a free Sunday email with the week's five most-upvoted stories ([past issues](https://buttondown.com/kiwi-news-weekly/archive/)) - [Guidelines](https://news.kiwistand.com/guidelines): what belongs on Kiwi News and how to comment - [Search](https://news.kiwistand.com/search?q=ethereum): search over stories (`q` is the query) - [Sitemap](https://news.kiwistand.com/sitemap.xml): all story pages (one sitemap per month) and all weekly pages - iOS app (beta): https://testflight.apple.com/join/6jyvYECH - MCP server for Claude and other MCP clients: [kiwimcp-client](https://github.com/attestate/kiwistand/tree/main/kiwimcp-client) (`npx kiwimcp-client`) Story pages live at `https://news.kiwistand.com/stories/?index=0x`. The `index` query parameter identifies the story; the slug comes from the title and is only cosmetic (wrong slugs redirect to the right one). Profiles live at `https://news.kiwistand.com/upvotes?address=`. ## How to cite Link to the canonical story URL (`https://news.kiwistand.com/stories/?index=0x`, given as `Kiwi News:` in `/stories/context` and as `` on the page) and name "Kiwi News" as the source. When summarizing the linked article itself, also cite the original article URL (the `Source:` line). For a week, cite `https://news.kiwistand.com/weekly/-W`. Refer to submitters and commenters by the display name shown (ENS, Farcaster or nickname). ## JSON API (no auth, CORS open) - [Hot feed](https://news.kiwistand.com/api/v1/feeds/hot): trending stories, `?page=0` paginates - [New feed](https://news.kiwistand.com/api/v1/feeds/new): newest stories - [Best feed](https://news.kiwistand.com/api/v1/feeds/best?period=week): top stories for a period - [Story](https://news.kiwistand.com/api/v1/stories?index=0x...): one story with its comments - [Profile](https://news.kiwistand.com/api/v1/profile/0x...): ENS name, avatar and social links of an address `/api/` is disallowed for crawlers in robots.txt (it is for apps, not for indexing); crawlers and agents that follow robots.txt should use the plain-text endpoints above. ## Developer guide Kiwi News is open source ([attestate/kiwistand](https://github.com/attestate/kiwistand)). It is a Node.js application using Express.js for the backend and server-side rendered views for the frontend. It uses modern JavaScript (ESM) and LMDB for its database. ### Protocol Overview Kiwi News is built on an open, permissionless, and decentralized peer-to-peer protocol. Key features include: - **P2P Content Distribution**: User-generated content is distributed across a P2P network. Nodes use a Merkle Patricia Trie to efficiently synchronize and verify data with each other. - **Open participation**: Any Ethereum address can post, upvote and comment in its own name. (Until 0.12.0 this required minting the "Hyperkiwification"/Kiwi Pass NFT on Optimism; that requirement is gone.) Passkey accounts are a browser-held key whose address acts as its own identity. - **Key Delegation**: Users can delegate signing authority from their main wallet (the `from` address, their identity) to a temporary, "hot" key (the `to` address). This allows for a smoother user experience without constant wallet confirmations for every action. The delegation is a transaction to the Delegator3 contract on OP Mainnet. For how it works, see [Delegation](https://news.kiwistand.com/docs/delegation). For a deeper dive into the protocol, see the docs at [news.kiwistand.com/docs](https://news.kiwistand.com/docs/): the [guide to how the protocol works](https://news.kiwistand.com/docs/guide), the [protocol specification](https://news.kiwistand.com/docs/protocol) and the [HTTP API](https://news.kiwistand.com/docs/http-api). They are also available as plain text for LLMs at [/docs/llms.txt](https://news.kiwistand.com/docs/llms.txt). ## IMPORTANT: API Architecture **The Kiwi News API is split across two different ports with distinct purposes:** ### Port 443 (Standard HTTPS) - Public Read API - **Base URL**: `https://news.kiwistand.com/api/v1` - **Purpose**: Public, read-only data access - **CORS**: Fully enabled (`Access-Control-Allow-Origin: *`) - **Endpoints**: - `GET /feeds/hot` - Trending stories with metadata - `GET /feeds/new` - Newest stories - `GET /feeds/best` - Top-rated stories - `GET /profile/:address` - User profile data (ENS, avatars, social links) - `GET /karma/:address` - User karma scores - `GET /stories?index=:index` - Single story details - `GET /stories/context?index=:index` - LLM-friendly plain-text rendering of a story (title, source URL, extracted article text, and comments) ### Port 8443 - Core API (Read/Write) - **Base URL**: `https://news.kiwistand.com:8443/api/v1` - **Purpose**: Core API for all read/write operations - **CORS**: Fully enabled (`Access-Control-Allow-Origin: *`) - **Endpoints**: - `POST /list` - Paginated list of messages - `GET /delegations` - Delegation mappings - `POST /messages` - Submit new messages (stories, comments, upvotes) ### For Client Developers (Recommended) If you only want to build a client against the Kiwi News API, use the public instances: - **Core API (Read/Write)**: `https://news.kiwistand.com:8443/api/v1` - **Public Feeds (Read-only)**: `https://news.kiwistand.com/api/v1` **Note**: Both ports send `Access-Control-Allow-Origin: *` (the `cors` middleware with default options in `src/http.mjs` and `src/api.mjs`), so browser apps can call either one directly. ### For Node Operators (Advanced) Running a full Kiwi News node requires syncing with the Ethereum blockchain, which can be resource-intensive. Please see the `readme.md` for details on requirements and the initial sync process. #### Local Development 1. Clone the repository. 2. Install dependencies: `npm install` 3. Create the environment file: `cp .env-copy .env` 4. Edit the `.env` file with your RPC node configuration. 5. Start the development server: `npm run watch` ### Common Commands - `npm run watch`: Starts the development server with file watching (most common). - `npm run dev`: A similar command to start the server. - `npm start`: Starts the production server. - `npm test`: Runs the test suite. - `npm run lint`: Checks for code formatting issues. ## Core Documentation - [Docs](https://news.kiwistand.com/docs/): the Kiwi News Protocol, the node's HTTP API, delegation and the changelog. - [CONVENTIONS.md](https://github.com/attestate/kiwistand/blob/main/CONVENTIONS.md): **Crucial Read.** Outlines the coding style, architecture, and conventions. Please adhere to these strictly. - [src/web/src/API.mjs](https://github.com/attestate/kiwistand/blob/main/src/web/src/API.mjs): **Crucial Client Reference.** Contains the frontend API logic, including EIP-712 signing details. - [src/api.mjs](https://github.com/attestate/kiwistand/blob/main/src/api.mjs): Defines the core Express.js API routes for the backend. - [contributing.md](https://github.com/attestate/kiwistand/blob/main/contributing.md): Guidelines on pull requests and the development process. ## Building a Kiwi News Client: A Quick Start Guide This guide provides everything you need to build a simple client that can read the Kiwi News feed and upvote a story. ### Quick Start: Upvoting a Story Here is a complete, language-agnostic overview of the process: 1. **Fetch the Feed**: Send a `POST` request to `https://news.kiwistand.com:8443/api/v1/list` with a JSON body like `{"from": 0, "amount": 10, "type": "amplify"}` to get the first 10 stories. 2. **Prepare the Message**: Let's say you want to upvote the first story from the feed. You'll create a JSON object with its `title` and `href`. 3. **Sign the Message**: Using an EIP-712 compliant library, sign the message object with the user's Ethereum private key. 4. **Submit the Upvote**: Send a `POST` request to `https://news.kiwistand.com:8443/api/v1/messages` with the message object from step 2, plus the `signature` from step 3. --- ## Client API: Step-by-Step Guide The Kiwi News API is versioned under `/api/v1` and split across two ports as described above. ### Core Concepts - **Message**: The fundamental unit of data on Kiwi News (e.g., a story submission, a comment, an upvote). - **Message ID (`index`)**: A unique identifier derived from the message's content and timestamp. It's a hex string prefixed with the timestamp, ensuring chronological and content-based uniqueness. - **`signer`**: The Ethereum address of the key that signed a message. This could be a user's main wallet or a delegated key. - **`identity`**: The address a message is attributed to: the signer itself, or the address that delegated to the signer. This is the true "author" of a message. If delegation is not used, the `signer` and `identity` will be the same. - **Comment href format**: Comments have an `href` field in the format `kiwi:` that links them to their parent story. ### Reading from the API #### Fetching Feeds (`/api/v1/list` on port 8443) To get a list of stories or comments, send a `POST` request. - **Endpoint**: `POST https://news.kiwistand.com:8443/api/v1/list` - **Request Body**: - `from` (integer): The starting offset for pagination. - `amount` (integer): The number of items to retrieve (maximum: 500 items per request). - `type` (string, optional): `"amplify"` for stories (default) or `"comment"` for comments. **Example Request:** ```bash curl -X POST https://news.kiwistand.com:8443/api/v1/list \ -H "Content-Type: application/json" \ -d '{"from": 0, "amount": 10, "type": "amplify"}' ``` **Example Response:** ```json { "status": "success", "code": 200, "message": "OK", "details": "Extracted posts from...", "data": [ { "index": "63b0bef1...", "href": "https://example.com/story", "signature": "0xfbc8c...", "timestamp": 1672527601, "title": "Example Story Title", "type": "amplify", "signer": "0x0DF5Ba...", "identity": "0x0DF5Ba...", "upvotes": 15, "upvoters": ["0x0DF5Ba..."], "commentCount": 3 } ] } ``` **Important Notes**: - The `/list` endpoint returns the data array directly in the `data` field - Maximum 500 items can be returned per request - Stories don't include their comments - to get a story with all its comments, use `/api/v1/stories?index=` #### Fetching Named Feeds (port 443) For curated feeds, use the GET endpoints on port 443: - **Endpoints**: - `GET https://news.kiwistand.com/api/v1/feeds/hot` - `GET https://news.kiwistand.com/api/v1/feeds/new` - `GET https://news.kiwistand.com/api/v1/feeds/best` - **Query Parameters**: `?page=0` for pagination - **Note**: CORS is enabled (`Access-Control-Allow-Origin: *`), so browser clients can call these directly **Response Format for /feeds/* endpoints:** ```json { "status": "success", "code": 200, "message": "OK", "details": "hot feed", "data": { "stories": [/* array of story objects */] } } ``` **Important Differences from /list**: - `/feeds/*` endpoints return data in `data.stories` array (nested structure) - `/list` endpoint returns data directly in `data` array (flat structure) **Feed Limitations**: - The `/best` feed can be empty for short periods (e.g. `day`); handle empty states. - The `/best` feed supports period filtering (`?period=day|week|month|year|all`) - longer periods are more likely to have content. #### Resolving Author Identity (`signer` vs. `identity`) When you receive a message, you need to determine the author's identity. Here is the required client-side logic: 1. **Check the `signer`**: The `signer` field is the address that created the signature. 2. **Check the `identity`**: The `identity` field is the author's address (the delegating wallet, or the signer itself). 3. **If `signer` === `identity`**: The user acted with their main wallet. 4. **If `signer` !== `identity`**: The user acted with a delegated key. The `identity` field shows you who the true author is. You can verify this delegation is valid by checking the `/api/v1/delegations` endpoint. #### Fetching a Single Story - **Endpoint**: `GET https://news.kiwistand.com/api/v1/stories?index=` - **Description**: Retrieves the full details for a single story, including all of its comments in the response. Comments are included directly in the story response object, not requiring a separate API call. #### Fetching a Story as LLM Context - **Endpoint**: `GET https://news.kiwistand.com/stories/context?index=` - **Response**: `text/plain; charset=utf-8` (Markdown, not JSON) - **Description**: Returns an LLM-friendly rendering of a story containing the title, the source URL, the extracted plain-text article body (when extraction succeeds), and all comments with their authors' display names. Designed to be pasted into or fetched by an LLM so it can answer questions about a Kiwi story without a client having to stitch together `/stories`, article extraction, and ENS resolution. The `index` query parameter must be the `0x`-prefixed message index, e.g. `/stories/context?index=0x63b0bef1...`. Cached for one hour. **Example:** ```bash curl "https://news.kiwistand.com/stories/context?index=0x63b0bef1..." ``` ### Writing to the API All write actions require an EIP-712 signature and must be sent to port 8443. #### The EIP-712 Signature Payload - **Domain**: ```json { "name": "kiwinews", "version": "1.0.0", "salt": "0xfe7a9d68e99b6942bb3a36178b251da8bd061c20ed1e795207ae97183b590e5b" } ``` - **Types**: ```json { "Message": [ { "name": "title", "type": "string" }, { "name": "href", "type": "string" }, { "name": "type", "type": "string" }, { "name": "timestamp", "type": "uint256" } ] } ``` #### Message `type` Field Reference - `"amplify"`: Used for both submitting a new story and upvoting an existing one. - `"comment"`: Used for posting a comment. The `href` should be `kiwi:`. - `"reaction"`: Used for emoji reactions on comments. The `href` should be `kiwi:`. #### Submitting a Message - **Endpoint**: `POST https://news.kiwistand.com:8443/api/v1/messages` - **Request Body**: A JSON object containing the message data (`title`, `href`, `type`, `timestamp`) plus the `signature`. **Example Request Body:** ```json { "title": "The Title of the Story Being Upvoted", "href": "https://example.com/the-story-url", "type": "amplify", "timestamp": 1678886400, "signature": "0x[... the long EIP-712 signature you generated ...]" } ``` ### Verifying Signatures (Client-Side) Your client should always verify the signature of messages received from the feed. **Example using `ethers.js`:** ```javascript import { verifyTypedData } from "ethers/lib/utils"; // The EIP-712 domain and types (as defined above) const domain = { /* ... */ }; const types = { /* ... */ }; // A message object from the /api/v1/list response const messageFromApi = { /* ... */ }; // Separate the signature from the message data const { signature, signer, ...messageToVerify } = messageFromApi; // Recover the signer's address const recoveredAddress = verifyTypedData(domain, types, messageToVerify, signature); // The recoveredAddress should match the `signer` field console.log("Is signature valid?", recoveredAddress.toLowerCase() === signer.toLowerCase()); ``` ### Handling API Errors - **Invalid Signature**: The API will return a `400 Bad Request` if the signature does not match the message and signer. - **Duplicate Message**: If a user tries to upvote the same story twice, the API will return a `400 Bad Request` with a detail message indicating it's a duplicate. ### Other Useful Endpoints (Port 8443) - **`GET /delegations`**: Retrieves an object of valid delegations in the form of `to:from`. - **`GET /profile/:address`**: Retrieves profile information (ENS, avatar) for an address. - **`GET /karma/:address`**: Returns the karma score for an address. ### Other Useful Endpoints (Port 443) - **`GET /profile/:address`**: Retrieves profile information (ENS, avatar) for an address. - **`GET /karma/:address`**: Returns the karma score for an address. - **`GET /feeds/:name`**: Retrieves a specific feed (`hot`, `new`, `best`). Supports `page` for pagination. ## Implementation Notes for Browser Clients ### CORS Handling Both ports allow cross-origin requests (`Access-Control-Allow-Origin: *`, preflights are cached for 24 hours), so browser clients can fetch the endpoints directly. A server-side proxy is optional, e.g. to add your own caching. ### Example Next.js Proxy Route (optional) ```javascript // app/api/proxy/feeds/[name]/route.ts export async function GET(request, { params }) { const { name } = params; const url = new URL(request.url); const page = url.searchParams.get('page') || '0'; const response = await fetch( `https://news.kiwistand.com/api/v1/feeds/${name}?page=${page}` ); const data = await response.json(); return NextResponse.json(data); } ``` ### Response Format All API responses follow a consistent structure: **Success Response:** ```json { "status": "success", "code": 200, "message": "OK", "details": "Description of what was returned", "data": { /* actual response data */ } } ``` **Error Response:** ```json { "status": "error", "code": 400, "message": "Bad Request", "details": "Detailed error message", "errors": [ /* validation errors if applicable */ ] } ``` ## Testing the API You can test the API endpoints using curl: ```bash # Get hot feed (port 443) curl https://news.kiwistand.com/api/v1/feeds/hot # Get stories list (port 8443) curl -X POST https://news.kiwistand.com:8443/api/v1/list \ -H "Content-Type: application/json" \ -d '{"from": 0, "amount": 20, "type": "amplify"}' # Get user profile (works on both ports) curl https://news.kiwistand.com/api/v1/profile/0x0DF5Ba52e8C055950AaAf5fcFE829020e898ee60 ``` ## Practical Implementation Examples ### Fetching a Story with Comments Since stories from `/list` or `/feeds/*` don't include comments, you need two API calls: **Step 1: Get the story from a feed** ```javascript // Get stories from the hot feed const feedResponse = await fetch('https://news.kiwistand.com/api/v1/feeds/hot'); const feedData = await feedResponse.json(); const stories = feedData.data.stories; const firstStory = stories[0]; ``` **Step 2: Get the full story with comments** ```javascript // Use the story's index to fetch it with comments const storyResponse = await fetch( `https://news.kiwistand.com/api/v1/stories?index=${firstStory.index}` ); const storyData = await storyResponse.json(); // storyData.data now contains the story with all comments included const storyWithComments = storyData.data; ``` ### Fetching Comments for Multiple Stories If you need comments for multiple stories, batch your requests efficiently: ```javascript const storiesWithComments = await Promise.all( stories.slice(0, 5).map(story => fetch(`https://news.kiwistand.com/api/v1/stories?index=${story.index}`) .then(res => res.json()) .then(data => data.data) ) ); ``` ## Common Pitfalls 1. **Using wrong port**: Remember port 8443 for core API, port 443 for public feeds 2. **CORS**: Both ports send `Access-Control-Allow-Origin: *`; no proxy is needed for browser access 3. **Missing required fields**: The API validates JSON schemas strictly 4. **Timestamp format**: Use UNIX timestamps in seconds, not milliseconds 5. **Signature format**: Ensure signatures include the "0x" prefix 6. **Case sensitivity**: Ethereum addresses should be compared case-insensitively 7. **Response structure differences**: Remember `/list` returns `data` array directly, while `/feeds/*` returns `data.stories` 8. **Pagination limits**: `/list` endpoint has a maximum of 500 items per request ## Comments **tim**: FYI, I coded an entire webapp with Claude Code max with this! In a few minutes, so quite some stuff can be done with it!