Velo serves as a secure middleman gateway, allowing autonomous AI publishing agents (e.g. writers, SEO bots) to publish blogs to client websites without storing or exposing write tokens.
Instead of giving third-party AI agents your direct Sanity database credentials, agents authenticate against Velo using a single global Webhook Secret. Velo looks up the target organization, validates licensing features, and safely routes content writes to that organization's isolated database.
Autonomous agents can post content to the centralized Velo Agent Publish endpoint:
| Header | Required | Value |
|---|---|---|
| Content-Type | Yes | application/json |
| Authorization | Yes | Bearer <AGENT_WEBHOOK_SECRET> |
| Field | Type | Required | Description |
|---|---|---|---|
| organization_id | String (UUID) | Yes | The target tenant's organization ID in Velo database. |
| title | String | Yes | The title of the blog post. |
| slug | String | Yes | URL-safe unique slug path (e.g. rise-of-agents). |
| metaDescription | String | No | SEO meta description for the blog post. |
| body | String (JSON) | Yes | The content of the blog post. Must be a stringified TipTap JSON document. |
Do NOT submit plain text or raw HTML in the body field. Velo's blog engine renders dynamic pages by parsing TipTap JSON. Sending flat text will crash the client's website with rendering exceptions.
The TipTap JSON schema represents documents as trees of node structures. Here is an example of what the raw JSON should look like before you stringify it:
{
"type": "doc",
"content": [
{
"type": "heading",
"attrs": { "level": 2 },
"content": [{ "type": "text", "text": "Post Subtitle" }]
},
{
"type": "paragraph",
"content": [
{ "type": "text", "text": "This is a paragraph containing some " },
{ "type": "text", "marks": [{ "type": "bold" }], "text": "bolded text" },
{ "type": "text", "text": "." }
]
}
]
}A reference integration script in Node.js for dispatcher services or autonomous AI agent runners.
import fetch from 'node-fetch';
const bodyDoc = {
type: 'doc',
content: [
{
type: 'heading',
attrs: { level: 2 },
content: [{ type: 'text', text: 'Securing the Agentic CMS Pipeline' }]
},
{
type: 'paragraph',
content: [{ type: 'text', text: 'Velo serves as a secure middleware...' }]
}
]
};
const payload = {
organization_id: 'c348e5dc-31d7-4581-8a39-9aad54d9d822',
title: 'Securing the Agentic CMS Pipeline',
slug: 'securing-the-agentic-cms-pipeline',
metaDescription: 'A technical overview of broker-based headless CMS publishing.',
body: JSON.stringify(bodyDoc) // Must be a stringified JSON document!
};
const response = await fetch('https://yourdomain.com/api/webhooks/agent-publish', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer ' + process.env.AGENT_WEBHOOK_SECRET
},
body: JSON.stringify(payload)
});
const result = await response.json();
console.log('Result:', result);Occurs if the webhook secret is invalid, missing, or mismatched.
The target organization exists, but the blog feature is disabled in Velo settings.
The organization has blog enabled, but is missing its Sanity API Credentials.