# 🤖 Velo AI Agent Publishing API Documentation

This document serves as the **absolute source of truth** for AI coding agents (and developers) tasked with publishing blog content autonomously via Velo.

Velo acts as a **secure broker** between autonomous agents and client websites. The agent does not need direct access to Sanity databases or write tokens. Instead, it sends standard HTTP requests to Velo, which validates the credentials and commits the content to the correct tenant's isolated Sanity instance.

---

## 🏛 Architectural Pattern: Secure Broker

```mermaid
sequenceDiagram
    participant AI Agent
    participant Velo Webhook
    participant Supabase DB
    participant Tenant Sanity CDN
    AI Agent->>Velo Webhook: POST /api/webhooks/agent-publish<br/>(Bearer Token + Org ID + Post Content)
    Velo Webhook->>Velo Webhook: Verify Global Webhook Secret
    Velo Webhook->>Supabase DB: Lookup Org by ID<br/>(Verify blog_feature_enabled & Get Sanity Keys)
    Supabase DB-->>Velo Webhook: Returns Sanity API Credentials
    Velo Webhook->>Tenant Sanity CDN: Create Post Document (with Slug & TipTap Body)
    Tenant Sanity CDN-->>Velo Webhook: Document Created (ID)
    Velo Webhook-->>AI Agent: 200 OK (Success, Post ID)
```

---

## 🛠 Integration Overview

To publish blog posts dynamically, make an HTTP POST request to the Velo Webhook Broker endpoint:

### **Endpoint URL**
`https://your-velo-domain.com/api/webhooks/agent-publish`

### **Method**
`POST`

---

## 📬 Request Headers

| Header | Required | Value |
| :--- | :--- | :--- |
| `Content-Type` | Yes | `application/json` |
| `Authorization` | Yes | `Bearer <AGENT_WEBHOOK_SECRET>` (Global secret shared between Velo and the agent) |

---

## 📦 Request Body (JSON)

| Field | Type | Required | Description |
| :--- | :--- | :--- | :--- |
| `organization_id` | String (UUID) | **Yes** | Target organization's unique ID in Velo (retrieved from dashboard organization settings). |
| `title` | String | **Yes** | Human-readable title of the blog post. |
| `slug` | String | **Yes** | URL-friendly unique slug (e.g. `10-ways-to-scale-nextjs`). |
| `metaDescription` | String | No | SEO description of the post. |
| `body` | String (Escaped JSON) | **Yes** | Blog post body content. **MUST** be a stringified TipTap JSON document structure. |

---

## 📝 Body Content Format (TipTap JSON)

Velo stores the `body` field of blog posts as **stringified TipTap JSON**. Passing simple plain text or raw HTML directly to the `body` field will cause frontend rendering pages to crash when they attempt to parse it. 

The AI Agent must format the blog content into TipTap's JSON schema, stringify it, and pass it under the `body` field.

### **TipTap JSON Schema Template**

```json
{
  "type": "doc",
  "content": [
    {
      "type": "heading",
      "attrs": { "level": 1 },
      "content": [
        { "type": "text", "text": "Post Heading" }
      ]
    },
    {
      "type": "paragraph",
      "content": [
        { "type": "text", "text": "This is a paragraph of the blog post with " },
        { "type": "text", "marks": [{ "type": "bold" }], "text": "bold text" },
        { "type": "text", "text": " inside it." }
      ]
    },
    {
      "type": "bulletList",
      "content": [
        {
          "type": "listItem",
          "content": [
            {
              "type": "paragraph",
              "content": [{ "type": "text", "text": "Bullet item number one." }]
            }
          ]
        }
      ]
    }
  ]
}
```

---

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

If instructing an AI agent to publish a post via Velo:

> "Publish a blog post via Velo's secure webhook broker. 
> 1. Make a POST request to 'https://your-velo-domain.com/api/webhooks/agent-publish'.
> 2. Attach the header 'Authorization: Bearer <AGENT_WEBHOOK_SECRET>'.
> 3. Construct a valid TipTap JSON document representing the blog post content (with headings, paragraphs, and list items).
> 4. Send the payload with 'organization_id', 'title', 'slug', 'metaDescription', and the stringified TipTap JSON under 'body'."

---

## 💻 Implementation Example (Node.js Script)

Here is a template script for an autonomous AI writing agent:

```typescript
import fetch from 'node-fetch';

const VELO_WEBHOOK_URL = 'https://velo.baseworks.co/api/webhooks/agent-publish';
const WEBHOOK_SECRET = process.env.AGENT_WEBHOOK_SECRET;
const ORG_ID = 'c348e5dc-31d7-4581-8a39-9aad54d9d822'; // Velo Organization UUID

async function publishAgentPost() {
  // 1. Write the blog post content in TipTap JSON format
  const tiptapDoc = {
    type: 'doc',
    content: [
      {
        type: 'heading',
        attrs: { level: 2 },
        content: [{ type: 'text', text: 'Why AI Agents represent the future of CMS' }]
      },
      {
        type: 'paragraph',
        content: [
          { type: 'text', text: 'By utilizing a secure middleware gateway like Velo, agents can commit content safely...' }
        ]
      }
    ]
  };

  // 2. Build the request body
  const payload = {
    organization_id: ORG_ID,
    title: 'The Rise of Agentic CMS Architectures',
    slug: 'rise-of-agentic-cms-architectures',
    metaDescription: 'An analysis of how secure broker gateways allow AI agents to safely write to enterprise CMS setups.',
    body: JSON.stringify(tiptapDoc), // Stringified TipTap JSON
  };

  // 3. Dispatch to Velo
  try {
    const response = await fetch(VELO_WEBHOOK_URL, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'Authorization': `Bearer ${WEBHOOK_SECRET}`,
      },
      body: JSON.stringify(payload),
    });

    const result = await response.json();
    if (response.ok && result.success) {
      console.log(`Success! Published post. ID: ${result.post_id}`);
    } else {
      console.error(`Failed to publish:`, result.error);
    }
  } catch (error) {
    console.error('Network or server error:', error);
  }
}

publishAgentPost();
```

---

## ★ Responses

### Success (200 OK)
```json
{
  "success": true,
  "message": "Post \"My AI-Written Post\" published successfully",
  "post_id": "drafts.70de1090-ffb8-4d57-9d7e-751ee1e695d7"
}
```

### Error Response Schema
```json
{
  "error": "Error message details"
}
```

---

## 💡 Troubleshooting & Error Mapping

| HTTP Status | Error Message | Description | Solution |
| :--- | :--- | :--- | :--- |
| `400` | `Invalid JSON body` | The request body is not valid JSON. | Check JSON syntax and quotes. |
| `400` | `Missing required fields: ...` | One of the mandatory fields is missing. | Include `organization_id`, `title`, `slug`, and `body`. |
| `401` | `Unauthorized: Invalid or missing secret` | The `Authorization` header is invalid or missing. | Ensure the header starts with `Bearer ` followed by the correct secret key. |
| `403` | `Blog feature is not enabled...` | The target tenant does not have the blog enabled. | Enable the blogging feature for this tenant in Velo. |
| `404` | `Organization not found` | The `organization_id` does not match any Velo tenant. | Double-check the organization UUID in the Velo Settings page. |
| `422` | `Organization does not have a properly configured blog backend` | The organization exists but has no Sanity keys. | Contact administrator to provision the client's Sanity database. |
| `502` | `Failed to publish post. The blogging backend...` | The client's Sanity project rejected the write request. | Check write token permissions or schema consistency. |
| `503` | `Webhook not configured` | The Velo server is missing the `AGENT_WEBHOOK_SECRET` environment variable. | Add the secret to Velo's environment config. |
