From f973b901023fce451072b4b110065a31ddd8f0c7 Mon Sep 17 00:00:00 2001 From: slobo Date: Wed, 7 Jan 2026 19:21:19 -0500 Subject: [PATCH] add llms.txt for ai-friendly api documentation (#75) * add plan for llm-friendly documentation * add llms.txt for ai-friendly api documentation * move llms.txt link to top of api docs * add copy for ai button to docs sidebar * remove undocumented claim-name, rename to copy for llms * add both mainnet and sepolia resolver addresses to llms.txt * simplify llms.txt link text --- PLAN-llm-friendly-docs.md | 554 ++++++++++++++++++++++++++++++++++++++ components/CopyForLLM.js | 35 +++ data/docs/api-routes.mdx | 2 + pages/docs/[[...slug]].js | 7 + public/llms.txt | 150 +++++++++++ public/sitemap.xml | 27 +- 6 files changed, 764 insertions(+), 11 deletions(-) create mode 100644 PLAN-llm-friendly-docs.md create mode 100644 components/CopyForLLM.js create mode 100644 public/llms.txt diff --git a/PLAN-llm-friendly-docs.md b/PLAN-llm-friendly-docs.md new file mode 100644 index 0000000..943a462 --- /dev/null +++ b/PLAN-llm-friendly-docs.md @@ -0,0 +1,554 @@ +# Plan: LLM-Friendly Documentation + +This document contains detailed implementation plans for making Namestone docs AI-friendly. + +--- + +## Plan 1: OpenAPI Specification + +### Overview + +Create a comprehensive OpenAPI 3.0 specification that formally describes the entire Namestone API. This is the highest-impact change for LLM compatibility. + +### File Location + +``` +public/openapi.yaml +``` + +Rationale: Files in `public/` are served statically by Next.js, making the spec accessible at `https://namestone.com/openapi.yaml` with zero additional routing configuration. + +### Endpoints to Document + +| Endpoint | Method | Auth Required | Category | +|----------|--------|---------------|----------| +| `/set-name` | POST | Yes | Names | +| `/set-names` | POST | Yes | Names (batch) | +| `/get-names` | GET | Optional | Names | +| `/search-names` | GET | Optional | Names | +| `/delete-name` | POST | Yes | Names | +| `/set-domain` | POST | Yes | Domains | +| `/get-domain` | GET | Optional | Domains | +| `/get-domains` | GET | No | Domains | +| `/enable-domain` | POST | No* | Domains | +| `/get-siwe-message` | GET | No | Auth | + +*enable-domain requires SIWE signature instead of API key + +### Key Decisions + +#### 1. Network Handling + +**Decision**: Use server variables, not separate paths. + +```yaml +servers: + - url: https://namestone.com/api/public_v1/{network} + variables: + network: + default: mainnet + enum: [mainnet, sepolia] + description: Ethereum network +``` + +Why: The current URL structure uses `/api/public_v1/mainnet/set-name` and `/api/public_v1/sepolia/set-name`. Using a server variable keeps the spec DRY and matches how the API actually works. + +#### 2. Authentication Schema + +```yaml +securityDefinitions: + ApiKeyHeader: + type: apiKey + in: header + name: Authorization + ApiKeyQuery: + type: apiKey + in: query + name: api_key +``` + +Note: Most endpoints accept either header or query param. Document both. + +#### 3. Reusable Components + +Define these schemas once: + +```yaml +components: + schemas: + TextRecords: + type: object + additionalProperties: + type: string + description: ENS text records (avatar, com.twitter, url, description, etc.) + example: + avatar: "https://example.com/avatar.png" + com.twitter: "username" + description: "My profile" + + CoinTypes: + type: object + additionalProperties: + type: string + description: Multi-chain addresses keyed by SLIP-0044 coin type + example: + "2147483785": "0x..." # Optimism + "2147492101": "0x..." # Base + + NameData: + type: object + properties: + name: + type: string + domain: + type: string + address: + type: string + text_records: + $ref: '#/components/schemas/TextRecords' + coin_types: + $ref: '#/components/schemas/CoinTypes' + + DomainData: + type: object + properties: + domain: + type: string + address: + type: string + contenthash: + type: string + nullable: true + text_records: + $ref: '#/components/schemas/TextRecords' + coin_types: + $ref: '#/components/schemas/CoinTypes' + + Error: + type: object + properties: + error: + type: string +``` + +### Edge Cases to Handle + +#### 1. Optional vs Conditional Parameters + +- `get-names`: Both `domain` and `address` are optional individually, but behavior changes based on what's provided +- Need to document: "If omitted, returns all subnames for all domains tied to your API key" + +#### 2. Boolean-ish Parameters + +Several endpoints use `1` or `0` instead of true/false: +- `text_records` in get-names: `1` or `0` (default 1) +- `exact_match` in search-names: `1` or `0` +- `cycle_key` in enable-domain: `"1"` or `"0"` + +```yaml +text_records: + type: integer + enum: [0, 1] + default: 1 + description: Whether to return text records. 1 = yes, 0 = no. +``` + +#### 3. Contenthash Format + +Document the expected format: +```yaml +contenthash: + type: string + description: IPFS or IPNS content hash + example: "ipfs://QmYourContentHash" + pattern: "^(ipfs|ipns)://.*$" +``` + +#### 4. Coin Type Values + +Coin types are strings containing large integers (SLIP-0044 format): +```yaml +coin_types: + type: object + additionalProperties: + type: string + pattern: "^0x[a-fA-F0-9]{40}$" + propertyNames: + pattern: "^[0-9]+$" + example: + "2147483785": "0x534631Bcf33BDb069fB20A93d2fdb9e4D4dD42CF" +``` + +#### 5. set-names Batch Limits + +```yaml +names: + type: array + maxItems: 50 + minItems: 1 + items: + $ref: '#/components/schemas/NameInput' +``` + +#### 6. Error Response Variations + +Different endpoints return errors differently. Document all patterns: + +```yaml +# Simple error +{"error": "Invalid domain"} + +# Batch error (set-names) +{ + "error": "Batch operation failed", + "processed": 0, + "errors": [{"index": 1, "name": "bad", "error": "Invalid ens name"}], + "total": 3 +} +``` + +### Site Presentation + +#### Option A: Link from Docs (Recommended) + +Add to `api-routes.mdx`: +```markdown +## Machine-Readable Specification + +For programmatic access, an OpenAPI 3.0 specification is available: + +- [OpenAPI Spec (YAML)](/openapi.yaml) +- [View in Swagger UI](https://petstore.swagger.io/?url=https://namestone.com/openapi.yaml) +``` + +#### Option B: Dedicated Page with Swagger UI + +Create `pages/api-spec.js` that renders Swagger UI. More work, but nicer UX. + +**Recommendation**: Start with Option A. Can add Swagger UI page later if demand exists. + +### Validation Strategy + +Before shipping: +1. Validate with `swagger-cli validate public/openapi.yaml` +2. Test each endpoint example actually works +3. Generate a TypeScript client and verify types match SDK + +### Implementation Steps + +1. Create `public/openapi.yaml` with info, servers, security +2. Add `/get-names` endpoint (simplest GET) +3. Add `/set-name` endpoint (POST with complex body) +4. Add remaining name endpoints +5. Add domain endpoints +6. Add auth endpoints (get-siwe-message, enable-domain) +7. Define all reusable components/schemas +8. Add comprehensive examples +9. Add error responses +10. Validate and test +11. Add link to docs page +12. Update sitemap.xml + +--- + +## Plan 2: llms.txt + +### Overview + +Create a plain-text file optimized for LLM consumption. This follows the emerging `llms.txt` convention (similar to robots.txt but for AI). + +### File Location + +``` +public/llms.txt +``` + +Accessible at: `https://namestone.com/llms.txt` + +### Design Principles + +1. **Single file** - Everything an LLM needs in one place +2. **Plain text** - No parsing required, can be injected directly into prompts +3. **Hierarchical** - Most important info first +4. **Copy-pasteable** - Examples should work as-is (except API key) +5. **Token-efficient** - Concise but complete (~2-4KB target) + +### Structure + +``` +# Namestone API + +[One-line description] + +## Quick Start +[Minimal working example] + +## Authentication +[How to auth] + +## Endpoints +[All endpoints with minimal description] + +## Common Operations +[Most frequent use cases with examples] + +## SDK +[How to use the TypeScript SDK] + +## Limits & Constraints +[Important limitations] + +## Links +[Where to find more] +``` + +### Edge Cases to Handle + +#### 1. Network Selection + +LLMs need to know about mainnet vs sepolia: +``` +## Networks +- Mainnet: https://namestone.com/api/public_v1/mainnet/ +- Sepolia (testnet): https://namestone.com/api/public_v1/sepolia/ + +Use Sepolia for testing. Switch to Mainnet for production. +``` + +#### 2. API Key Placeholder + +Use consistent placeholder: +``` +Authorization: YOUR_API_KEY +``` + +Not `` or `{api_key}` - LLMs sometimes include angle brackets literally. + +#### 3. Resolver Requirement + +Critical prerequisite that's easy to miss: +``` +## Prerequisites +Your ENS domain must use Namestone's resolver: +0xA87361C4E58B619c390f469B9E6F27d759715125 + +Set this in your ENS manager before using the API. +``` + +#### 4. Coin Type Complexity + +Multichain is complex. Keep it simple in llms.txt: +``` +## Multichain Addresses +coin_types maps SLIP-0044 coin type IDs to addresses. +Common IDs: +- 2147483785 = Optimism +- 2147492101 = Base +- 2147525809 = Arbitrum + +Convert chain_id to coin_type: 0x80000000 | chain_id +``` + +#### 5. set-name vs claim-name + +Important distinction: +``` +## Creating Names +- set-name: Creates or overwrites. Use for admin operations. +- claim-name: Fails if exists. Use for user self-service. +``` + +#### 6. Private Names + +``` +## Privacy +Names can be marked private in the admin panel. +Private names only appear in get-names when using your API key. +``` + +### Content Draft + +``` +# Namestone API + +Gasless ENS subdomain management. Issue subdomains like alice.yourbrand.eth via REST API. + +## Quick Start + +curl -X POST \ + -H "Content-Type: application/json" \ + -H "Authorization: YOUR_API_KEY" \ + -d '{"domain": "yourbrand.eth", "name": "alice", "address": "0x..."}' \ + https://namestone.com/api/public_v1/mainnet/set-name + +## Authentication + +API key in header: Authorization: YOUR_API_KEY +Or query param: ?api_key=YOUR_API_KEY + +Get an API key: https://namestone.com/try-namestone + +## Networks + +- Mainnet: /api/public_v1/mainnet/ +- Sepolia: /api/public_v1/sepolia/ + +## Endpoints + +### Names (Subdomains) +POST /set-name - Create/update subdomain +POST /set-names - Batch create/update (max 50) +POST /claim-name - Create subdomain (fails if exists) +GET /get-names - List subdomains by domain/address +GET /search-names - Search by prefix +POST /delete-name - Delete subdomain + +### Domains +POST /set-domain - Set domain records +GET /get-domain - Get domain records +GET /get-domains - List domains by admin address +POST /enable-domain - Enable domain (requires SIWE) +GET /get-siwe-message - Get SIWE message for signing + +## Common Operations + +### Issue subdomain to user +POST /set-name +{ + "domain": "yourbrand.eth", + "name": "alice", + "address": "0x1234...", + "text_records": { + "avatar": "https://...", + "com.twitter": "alice" + } +} + +### Look up user's subdomain +GET /get-names?domain=yourbrand.eth&address=0x1234... + +### Search subdomains +GET /search-names?domain=yourbrand.eth&name=ali + +### Delete subdomain +POST /delete-name +{"domain": "yourbrand.eth", "name": "alice"} + +## SDK (TypeScript) + +npm install @namestone/namestone-sdk + +import Namestone from "@namestone/namestone-sdk"; +const ns = new Namestone("YOUR_API_KEY"); + +await ns.setName({ + domain: "yourbrand.eth", + name: "alice", + address: "0x..." +}); + +const names = await ns.getNames({ domain: "yourbrand.eth" }); + +## Multichain Addresses + +Add L2 addresses via coin_types (SLIP-0044 format): +{ + "coin_types": { + "2147483785": "0x...", // Optimism + "2147492101": "0x...", // Base + "2147525809": "0x..." // Arbitrum + } +} + +Convert chain_id to coin_type: 0x80000000 | chain_id + +## Limits + +- Batch operations: 50 names max +- Default pagination: 50 results +- Max pagination: 1000 results + +## Prerequisites + +Domain must use Namestone resolver: +0xA87361C4E58B619c390f469B9E6F27d759715125 + +## Links + +- Docs: https://namestone.com/docs +- SDK: https://github.com/namestonehq/namestone-sdk +- Admin Panel: https://namestone.com/admin +- Get API Key: https://namestone.com/try-namestone +``` + +### Site Presentation + +#### robots.txt Addition + +Add to `public/robots.txt` (create if doesn't exist): +``` +User-agent: * +Allow: / + +# LLM-friendly documentation +# See: https://namestone.com/llms.txt +``` + +#### Docs Link + +Add to `api-routes.mdx`: +```markdown +## AI/LLM Integration + +For AI assistants and code generation tools: +- [llms.txt](/llms.txt) - Concise API reference for LLMs +- [OpenAPI Spec](/openapi.yaml) - Machine-readable specification +``` + +#### Meta Tag (Optional) + +In `_document.js` or layout: +```html + +``` + +This is speculative (no standard yet) but forward-compatible. + +### Implementation Steps + +1. Create `public/llms.txt` with full content +2. Test by pasting into Claude/ChatGPT and asking it to generate code +3. Iterate based on what the LLMs get wrong +4. Create/update `public/robots.txt` +5. Add links to docs +6. Update sitemap.xml + +--- + +## Implementation Order + +**Phase 1: llms.txt** (1-2 hours) +- Lower complexity +- Immediate value +- Easy to iterate + +**Phase 2: OpenAPI Spec** (4-6 hours) +- Higher complexity +- Requires validation +- More maintenance burden + +--- + +## Success Metrics + +After implementation, test with: + +1. **Claude Code**: "Using the Namestone API, write code to issue a subdomain" +2. **Cursor**: Import openapi.yaml, verify autocomplete works +3. **ChatGPT**: Paste llms.txt, ask for integration code + +The docs are LLM-friendly if: +- LLMs generate working code on first try +- No hallucinated endpoints or parameters +- Correct authentication handling +- Proper network selection (mainnet vs sepolia) diff --git a/components/CopyForLLM.js b/components/CopyForLLM.js new file mode 100644 index 0000000..9608427 --- /dev/null +++ b/components/CopyForLLM.js @@ -0,0 +1,35 @@ +import { useState } from "react"; +import { Icon } from "@iconify/react"; + +export default function CopyForLLM() { + const [copied, setCopied] = useState(false); + const [loading, setLoading] = useState(false); + + const handleCopy = async () => { + setLoading(true); + try { + const response = await fetch("/llms.txt"); + const text = await response.text(); + await navigator.clipboard.writeText(text); + setCopied(true); + setTimeout(() => setCopied(false), 2000); + } catch (err) { + console.error("Failed to copy:", err); + } + setLoading(false); + }; + + return ( + + ); +} diff --git a/data/docs/api-routes.mdx b/data/docs/api-routes.mdx index e4c4cfe..4820ed5 100644 --- a/data/docs/api-routes.mdx +++ b/data/docs/api-routes.mdx @@ -3,6 +3,8 @@ NameStone's API lets you grant names (subdomains), set text records, and retrieve information about domains. To use our API, you'll need an API key for authentication. +See [llms.txt](/llms.txt) for a machine-readable API reference. + ## Get Started Use this [form](https://namestone.com/try-namestone) to enable your domain and receive a free API key via email, or enable your name programmatically via the [enable-domain](./enable-domain) endpoint. Your domain's resolver must be set to: diff --git a/pages/docs/[[...slug]].js b/pages/docs/[[...slug]].js index cb47fe7..a0e98c0 100644 --- a/pages/docs/[[...slug]].js +++ b/pages/docs/[[...slug]].js @@ -3,6 +3,7 @@ import { PageSEO } from "../../components/SEO"; import ReactMarkdown from "react-markdown"; import Link from "next/link"; import Header from "../../components/Header"; +import CopyForLLM from "../../components/CopyForLLM"; import { Icon } from "@iconify/react"; import { useState } from "react"; import { Prism as SyntaxHighlighter } from "react-syntax-highlighter"; @@ -111,6 +112,9 @@ export default function Docs({ content, fileName }) { Sepolia +
+ +
{Object.keys(navDict).map((item) => (
+
+ +
{Object.keys(navDict).map((item) => (
{success:true} +POST /set-names {domain,names[]} -> {success,processed,results[]} +POST /delete-name {domain,name} -> {success:true} +GET /get-names ?domain&address&limit=50&offset=0 -> [{name,domain,address,text_records?,coin_types?}] +GET /search-names ?domain&name&exact_match=0&limit=50 -> [{name,domain,address,...}] +POST /set-domain {domain,address?,text_records?,coin_types?} -> {success:true} +GET /get-domain ?domain -> [{domain,address,text_records,coin_types}] +GET /get-domains ?admin-address -> [{domain,address,...}] + +## Authentication + +Get API key: https://namestone.com/try-namestone +Use in header: Authorization: YOUR_API_KEY +Or query param: ?api_key=YOUR_API_KEY + +## URLs + +Mainnet: https://namestone.com/api/public_v1/{endpoint} +Sepolia: https://namestone.com/api/public_v1_sepolia/{endpoint} + +## Example: Create Subdomain + +curl -X POST \ + -H "Content-Type: application/json" \ + -H "Authorization: YOUR_API_KEY" \ + -d '{"domain":"brand.eth","name":"alice","address":"0x1234..."}' \ + https://namestone.com/api/public_v1/set-name + +Response: {"success": true} + +## Example: With Profile Data + +curl -X POST \ + -H "Content-Type: application/json" \ + -H "Authorization: YOUR_API_KEY" \ + -d '{ + "domain": "brand.eth", + "name": "alice", + "address": "0x1234...", + "text_records": { + "avatar": "https://example.com/pic.jpg", + "com.twitter": "alice", + "description": "Alice profile" + } + }' \ + https://namestone.com/api/public_v1/set-name + +## Example: Lookup by Address + +curl -H "Authorization: YOUR_API_KEY" \ + "https://namestone.com/api/public_v1/get-names?domain=brand.eth&address=0x1234..." + +Response: +[{ + "name": "alice", + "domain": "brand.eth", + "address": "0x1234...", + "text_records": {"avatar": "...", "com.twitter": "alice"} +}] + +## Example: Search Autocomplete + +curl "https://namestone.com/api/public_v1/search-names?domain=brand.eth&name=ali" + +Returns all names starting with "ali" (alice, alicia, etc.) + +## SDK (TypeScript) + +npm install @namestone/namestone-sdk + +import NameStone from "@namestone/namestone-sdk"; + +// Mainnet +const ns = new NameStone("YOUR_API_KEY"); + +// Sepolia testnet +const ns = new NameStone("YOUR_API_KEY", { network: "sepolia" }); + +// Create subdomain +await ns.setName({ + domain: "brand.eth", + name: "alice", + address: "0x1234...", + text_records: { "com.twitter": "alice" } +}); + +// Lookup +const names = await ns.getNames({ domain: "brand.eth", address: "0x1234..." }); + +// Search +const results = await ns.searchNames({ domain: "brand.eth", name: "ali" }); + +// Delete +await ns.deleteName({ domain: "brand.eth", name: "alice" }); + +## When to Use Which + +Creating subdomain? +- Single name -> setName (overwrites if exists) +- Bulk import -> setNames (batch up to 50) + +Looking up subdomains? +- By domain -> getNames({ domain }) +- By address -> getNames({ address }) +- Autocomplete -> searchNames({ domain, name: prefix }) +- Exact check -> searchNames({ domain, name, exact_match: true }) + +## Common Mistakes + +1. Missing resolver setup + Domain must use Namestone resolver. Set in ENS manager BEFORE calling API. + Mainnet: 0xA87361C4E58B619c390f469B9E6F27d759715125 + Sepolia: 0x467893bFE201F8EfEa09BBD53fB69282e6001595 + +2. Wrong URL for testnet + Mainnet: /api/public_v1/... + Sepolia: /api/public_v1_sepolia/... + +3. Ignoring pagination + Default limit is 50. If you get exactly 50 results, there may be more. + Use offset parameter to paginate. + +4. Forgetting auth header + Most endpoints need Authorization header + get-names works without auth but won't show private names + +## Multichain (Advanced) + +Add L2 addresses via coin_types using SLIP-0044 format: + +"coin_types": { + "2147483785": "0x...", // Optimism (0x80000000 | 10) + "2147492101": "0x...", // Base (0x80000000 | 8453) + "2147525809": "0x..." // Arbitrum (0x80000000 | 42161) +} + +Formula: coin_type = 0x80000000 | chain_id + +## Links + +Docs: https://namestone.com/docs +SDK: https://github.com/namestonehq/namestone-sdk +Admin: https://namestone.com/admin +Get Key: https://namestone.com/try-namestone diff --git a/public/sitemap.xml b/public/sitemap.xml index 8d6dab1..35e388d 100644 --- a/public/sitemap.xml +++ b/public/sitemap.xml @@ -1,17 +1,22 @@ - - - - + + + + https://namestone.com/ 2025-03-13T17:25:41+00:00 1.00 + + https://namestone.com/llms.txt + 2026-01-07T00:00:00+00:00 + 0.80 + https://namestone.com/docs 2025-03-13T17:25:41+00:00 @@ -176,7 +181,7 @@ https://namestone.com/blog/ens-subdomains 2025-03-13T17:25:41+00:00 0.64 - - - + + + \ No newline at end of file