# 🚀 Velo Blog Engine (Sanity) API Documentation

This document serves as the absolute source of truth for AI coding agents tasked with integrating frontend websites with the Velo Blog Engine. 

Velo uses a highly secure, multi-tenant Headless CMS approach. Every client (tenant) has their own completely isolated Sanity database.

---

## 📋 Prerequisites & Environment
Before starting, ensure your frontend environment has the following variable configured:
* `NEXT_PUBLIC_VELO_ORG_KEY`: Your Velo organization's public lead key (e.g., `org_1234abc`).

---

## 🛠 Integration Workflow

### **Step 1: Identify the Tenant (Velo Gateway)**
The frontend must securely fetch the tenant's Sanity configuration from the Velo API using your Org Key. 

```typescript
const VELO_API_URL = "https://fryqsyakqtnvprcyryvr.supabase.co/functions/v1/blog-config";
const ORG_KEY = process.env.NEXT_PUBLIC_VELO_ORG_KEY;

// Fetch the Sanity project details securely from Velo
const response = await fetch(VELO_API_URL, {
  method: 'GET',
  headers: {
    'x-org-key': ORG_KEY!
  }
});

const data = await response.json();

if (!data.success || !data.config) {
  throw new Error("Organization or Blog database not found");
}

const org = data.config;
```

### **Step 2: Dynamically Fetch Content from Sanity**
Velo blog posts are stored under the `post` document type. Use the `sanity_project_id` retrieved in Step 1 to initialize the client and fetch directly from Sanity's high-speed CDN.

```typescript
import { createClient } from '@sanity/client'

const sanityClient = createClient({
  projectId: org.sanity_project_id, 
  dataset: org.sanity_dataset || 'production',
  apiVersion: '2024-05-15',
  useCdn: true,
});

// IMPORTANT: Always filter by status == "published"
const query = `*[_type == "post" && status == "published"] | order(publishedAt desc) {
  _id,
  title,
  "slug": slug.current,
  mainImage {
    asset->{ url }
  },
  publishedAt,
  body, // TipTap JSON format
  seo
}`;

const posts = await sanityClient.fetch(query);
```

---

## 📝 Content Rendering (TipTap JSON)

**CRITICAL:** Unlike standard Sanity setups, Velo stores the `body` field as **TipTap JSON** stringified. 

To render this content, you must:
1. Parse the JSON if it's a string: `const content = JSON.parse(post.body)`.
2. Use a TipTap-compatible renderer. If building a React site, you can use `@tiptap/extension-document`, `@tiptap/extension-paragraph`, etc., or a utility to convert JSON to HTML.

Example rendering logic:
```typescript
import { generateHTML } from '@tiptap/html'
import StarterKit from '@tiptap/starter-kit'

const jsonContent = typeof post.body === 'string' ? JSON.parse(post.body) : post.body;
const html = generateHTML(jsonContent, [StarterKit]);

// In your component:
<div dangerouslySetInnerHTML={{ __html: html }} className="prose" />
```

---

## 🤖 AI IDE Agent Prompt (Cursor/Windsurf)

Use this prompt to have your AI agent integrate the Velo Blog Engine into your site:

> "Integrate the Velo Blog Engine into this [Next.js/React] project. First, securely fetch the Sanity configuration by making a GET request to 'https://fryqsyakqtnvprcyryvr.supabase.co/functions/v1/blog-config' using the 'x-org-key' header populated with the 'NEXT_PUBLIC_VELO_ORG_KEY' environment variable. Then, use the returned 'sanity_project_id' to fetch all 'published' posts directly from Sanity. Finally, render the 'body' field by parsing it as TipTap JSON and using a compatible renderer like '@tiptap/html'. Ensure the UI is responsive and follows a [Modern/Minimalist] aesthetic."

---

## 📅 Important Notes for AI Agents:

*   **Multi-Tenancy via Gateway:** Never hardcode a `projectId` or query Supabase directly. Always fetch the config from the Velo Gateway Edge Function.
*   **Status Filter:** Always include `status == "published"` in your GROQ queries to avoid showing draft content.
*   **Images:** Use the `mainImage.asset.url` for direct image links.
*   **Security:** Only read-access is required for the frontend. Do not use or expose any admin/service keys.
