Skip to content
ESC

Searching...

Quick Links

Type to search • Press to navigate • Enter to select

Keep typing to search...

No results found

No documentation matches ""

Agent API.

Authenticate external AI agents and automation tools to manage your VoxelSite pages, settings, assets, and publishing via REST endpoints. Includes OpenAPI 3.0 schema for tool calling.

Mar 16, 2026

Agent API

⚠️ Beta — The Agent API is in beta. The endpoints are stable and production-safe, but we're actively collecting feedback to refine the developer experience. If you encounter unexpected behavior, check the Logging & Debugging section below and reach out to support with your log file. Breaking changes, if any, will be communicated before release.

The Agent API lets external tools manage your VoxelSite installation programmatically. Use it to connect AI agents, automation platforms (Zapier, Make.com, n8n), or custom scripts to your site.

💡 Machine-readable schema available: The API publishes an OpenAPI 3.0 specification at /_studio/api/agent/v1/schema — no authentication required. Import it into any agent framework, Postman, or code generator. See the Tool Calling & Schema section below for integration examples. Live demo: demo.voxelsite.com/_studio/api/agent/v1/schema

How It Works

sequenceDiagram
    participant Agent as Your Agent / Tool
    participant API as Agent API
    participant Site as VoxelSite

    Note over Agent: 1. Discover capabilities
    Agent->>API: GET /schema (public, no auth)
    API-->>Agent: OpenAPI 3.0 schema (JSON)

    Note over Agent: 2. Authenticate
    Agent->>API: GET /pages (Authorization: Bearer vxs_...)
    Note over API: Validate key → check scope → route
    API-->>Agent: { data: { pages: [...] } }

    Note over Agent: 3. Make changes
    Agent->>API: POST /pages { slug, title, content }
    API->>Site: Create page, update nav, create revision
    API-->>Agent: { data: { page: {...} } }

    Agent->>API: POST /compile
    API->>Site: Recompile Tailwind CSS
    Agent->>API: POST /publish
    API->>Site: Publish preview → production

Agent API vs MCP

VoxelSite exposes two machine-readable interfaces. They serve different purposes:

Agent API MCP Endpoint
Purpose Manage the site (create pages, change settings, publish) Query the site (business info, menus, forms)
Auth Bearer token (API key) None (public)
Audience Site owner's tools and automations Any AI agent on the internet
Endpoint /_studio/api/agent/v1/ /mcp.php
Write access Yes Limited (form submissions only)
Schema OpenAPI 3.0 at /schema MCP tool definitions

The MCP endpoint serves public, read-only data. The Agent API is a private management surface behind authentication.

Enabling the Agent API

The Agent API is disabled by default. To enable it:

  1. Open Settings → API Access
  2. Toggle Enable Agent API on
  3. Click Save

The Agent API is blocked in Demo Mode. Remove the .demo file first if you need to enable it.

Creating API Keys

  1. In Settings → API Access, click Generate Key
  2. Enter a label (e.g. "My Website Automation", "Zapier Production")
  3. Select a role — this sets the maximum permissions:
Role Default scopes
Agent Pages (read/write), assets (read/write), compile, publish, submissions, settings (read), tools
Editor Pages (read/write), assets (read/write), compile, submissions, tools
Viewer Pages (read), settings (read), submissions, assets (read)
  1. Click Generate

Your API key is shown once. Copy it immediately — it cannot be viewed again.

Keys start with vxs_ and are stored as SHA-256 hashes. VoxelSite never stores the plaintext key.

Authentication

Include your API key as a Bearer token in the Authorization header:

curl -H "Authorization: Bearer vxs_your_key_here" \
     https://yourdomain.com/_studio/api/agent/v1/pages

Invalid or missing keys return 401 (codes: no_header, malformed, invalid_key). Rate-limited keys return 429 (code: rate_limited).

Every response includes rate limit headers:

X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 998
X-RateLimit-Reset: 1710612000

Complete Endpoint Reference

All endpoints are prefixed with /_studio/api/agent/v1/. 14 operations total across 7 resource groups.

Pages

Full CRUD for site pages. Renaming automatically rewrites all internal links and navigation references across the entire site. Deleting cleans up nav items and link references.

Method Path Scope Description
GET /pages pages:read List all pages (paginated)
GET /pages/:slug pages:read Get a single page with content
POST /pages pages:write Create a new page
PUT /pages/:slug pages:write Update page content or rename
DELETE /pages/:slug pages:write Delete a page

POST /pages — Create

Field Type Required Description
slug string URL slug (auto-normalized: lowercased, special chars removed)
title string Human-readable page title
content string Full PHP/HTML page content

Returns: 201 with { data: { page: { slug, title, file_path } } }

PUT /pages/:slug — Update

Field Type Required Description
title string New page title
content string New PHP/HTML content
slug string New slug (triggers site-wide link rewrite)

Returns: 200 with { data: { page: { slug, title, file_path }, renamed: bool, suggested_prompt?: string } }

Error codes

HTTP Code When
404 not_found Page slug doesn't exist
409 conflict Slug already taken (create or rename)
422 validation_error Empty slug, invalid characters
422 invalid_path Path traversal attempt (../)

Build & Publish

Method Path Scope Description
POST /compile compile:trigger Recompile Tailwind CSS
POST /publish publish:trigger Publish preview → production

POST /publish

Field Type Default Description
create_snapshot boolean true Create a snapshot backup before publishing

Returns: { data: { published: true, snapshot_id, files_copied, published_at } }

HTTP Code When
422 nothing_to_publish No changes since last publish
500 publish_failed File system or pipeline error

Settings

Method Path Scope Description
GET /settings settings:read Read settings (sensitive values redacted)
PUT /settings settings:write Update settings (whitelisted keys only)

Readable settings (GET)

site_name, site_tagline, site_language, site_url, site_favicon, ai_provider, nav_style, mobile_nav_style, footer_style, auto_snapshot, max_snapshots, max_revisions, last_published_at, publish_count, agent_api_enabled, agent_api_allowed_origins

Writable settings (PUT)

site_name, site_tagline, site_language, site_url, site_favicon, nav_style, mobile_nav_style, footer_style, auto_snapshot, max_snapshots, max_revisions

Security: AI API keys, agent_api_enabled, and agent_api_allowed_origins cannot be changed via the Agent API. These are management-plane settings that require human access through the Studio UI.

Response: { data: { updated: ["site_name"], rejected: ["agent_api_enabled"] } }


Submissions

Method Path Scope Description
GET /submissions submissions:read List form and action submissions

Query parameters

Parameter Type Description
form_id string Filter by form ID. Prefix with action_ for action submissions
status string Filter by status (new, read, etc.)
source string form, action, or omit for both
page integer Page number (default: 1)
per_page integer Results per page (1–100, default: 50)

Pagination strategy

When filtering by a single source (?source=form), pagination is applied at the database level for efficiency. When mixing sources (default), all matching rows from both databases are collected, globally sorted by created_at DESC, then paginated once — ensuring stable, duplicate-free page boundaries across sources.


Assets

Method Path Scope Description
GET /assets assets:read List uploaded assets
POST /assets assets:write Upload a file (max 10 MB)

GET /assets parameters

Parameter Type Description
category string Filter: images, css, js, fonts, files
page integer Page number
per_page integer Results per page (1–100)

POST /assets (multipart/form-data)

Field Type Required Description
file binary The file to upload (max 10 MB)
category string Target category (auto-detected from extension if omitted)

Blocked types: .php, .phtml, .phar, .exe, .bat, .sh, .py, .rb, .htaccess, etc.

Returns: 201 with { data: { path, filename, original, extension, category, size, width?, height? } }


Tools

Method Path Scope Description
GET /tools tools:invoke List available tools
POST /tools/invoke tools:invoke Invoke a tool by name

Built-in tools

Tool Description Parameters
get_business_info Business name, contact, hours None
get_menu Restaurant menu data category (optional filter)
get_services Service listings with pricing None
get_faq FAQ entries query (optional search)
list_forms All forms with field summaries None
get_form_schema Full field definitions for a form form_id (required)
submit_form Submit data, validated against schema form_id, data (required)

Plus any custom Actions configured in the Studio (booking, reservation, etc.).

POST /tools/invoke

{
  "name": "submit_form",
  "arguments": {
    "form_id": "contact",
    "data": {
      "name": "Jane Doe",
      "email": "[email protected]",
      "message": "Hello!"
    }
  }
}

Response Format

Success:

{
  "data": {
    "pages": [...],
    "total": 5,
    "page": 1,
    "per_page": 50
  }
}

Error:

{
  "error": {
    "code": "invalid_key",
    "message": "Invalid API key. The key may have been revoked or never existed."
  }
}

Error Codes

HTTP Code Meaning
401 no_header No Authorization header was sent
401 malformed Header present but not in Bearer vxs_... format
401 invalid_key Key not found or revoked
403 feature_disabled Agent API is disabled or demo mode is active
403 insufficient_scope Key lacks the required scope for this endpoint
404 not_found No endpoint matches the request method and path
409 conflict Resource already exists (e.g. page slug taken)
422 validation_error Invalid input data
429 rate_limited Hourly rate limit exceeded (check X-RateLimit-Reset)
500 publish_failed / server_error Internal error
503 service_unavailable Auth service could not initialize

Tool Calling & Schema

The Agent API publishes an OpenAPI 3.0.3 schema that any AI agent framework can consume for tool calling. This is the machine-readable contract that makes the API agent-compatible.

Schema Endpoint

# Public endpoint — no authentication required
curl https://yourdomain.com/_studio/api/agent/v1/schema

Returns the complete OpenAPI specification in JSON, describing every endpoint, parameter, request body, response shape, and error code.

Integration with Agent Frameworks

flowchart LR
    subgraph setup ["One-time Setup"]
        A["Agent starts"] --> B["GET /schema"]
        B --> C["Parse OpenAPI spec"]
        C --> D["Register as tools"]
    end

    subgraph runtime ["Runtime"]
        E["User prompt"] --> F["LLM selects tool"]
        F --> G["Agent calls API"]
        G --> H["API executes & returns"]
        H --> I["LLM processes result"]
    end

    setup --> runtime

OpenAI Function Calling

import openai, requests, json

BASE = "https://yourdomain.com/_studio/api/agent/v1"
KEY  = "vxs_your_key_here"

# 1. Fetch the schema (once at startup)
spec = requests.get(f"{BASE}/schema").json()

# 2. Convert to OpenAI tools format
tools = []
for path, methods in spec["paths"].items():
    for method, op in methods.items():
        params = {}
        if "requestBody" in op:
            body_schema = op["requestBody"]["content"]["application/json"]["schema"]
            params = body_schema.get("properties", {})
        tools.append({
            "type": "function",
            "function": {
                "name": op["operationId"],
                "description": f"{op['summary']}\n\nHTTP: {method.upper()} {path}",
                "parameters": {
                    "type": "object",
                    "properties": params,
                    "required": body_schema.get("required", []) if "requestBody" in op else [],
                },
            },
        })

# 3. Pass to the LLM
response = openai.chat.completions.create(
    model="gpt-4",
    messages=[{"role": "user", "content": "Create an About page"}],
    tools=tools,
)

# 4. Execute the tool call
call = response.choices[0].message.tool_calls[0]
method_path = call.function.description.split("HTTP: ")[1]
method, api_path = method_path.split(" ", 1)

result = requests.request(
    method=method,
    url=f"{BASE}{api_path}",
    headers={"Authorization": f"Bearer {KEY}", "Content-Type": "application/json"},
    json=json.loads(call.function.arguments),
)
print(result.json())

Anthropic/Claude Tool Use

import anthropic, requests, json

BASE = "https://yourdomain.com/_studio/api/agent/v1"
KEY  = "vxs_your_key_here"

# Convert schema to Claude tools
spec = requests.get(f"{BASE}/schema").json()
tools = []
for path, methods in spec["paths"].items():
    for method, op in methods.items():
        tool = {
            "name": op["operationId"],
            "description": f"{op['summary']}\n\nHTTP: {method.upper()} {path}",
            "input_schema": {"type": "object", "properties": {}, "required": []},
        }
        if "requestBody" in op and "application/json" in op["requestBody"].get("content", {}):
            schema = op["requestBody"]["content"]["application/json"]["schema"]
            tool["input_schema"]["properties"] = schema.get("properties", {})
            tool["input_schema"]["required"] = schema.get("required", [])
        tools.append(tool)

client = anthropic.Anthropic()
response = client.messages.create(
    model="claude-sonnet-4-20250514",
    max_tokens=1024,
    tools=tools,
    messages=[{"role": "user", "content": "List all pages on my site"}],
)

What the Schema Contains

Section Contents
info API name, version, description
servers Base URL (/_studio/api/agent/v1)
security Bearer authentication scheme
components.schemas Reusable data models: Page, Asset, Submission, Tool, ErrorResponse
paths Every endpoint with method, parameters, request body schema, response schema, and error codes

Scopes Reference

Scope Grants
pages:read Read page list and content
pages:write Create, update, and delete pages
settings:read Read settings (redacted)
settings:write Update whitelisted settings
compile:trigger Trigger Tailwind CSS compilation
publish:trigger Publish preview to production
submissions:read Read form and action submissions
assets:read List uploaded assets
assets:write Upload new assets
tools:invoke List and invoke tools

CORS Origins

By default, the Agent API accepts requests from any origin (*). To restrict access:

  1. Open Settings → API Access
  2. In the Allowed Origins textarea, enter one origin per line:
https://yourdomain.com
https://app.zapier.com
https://hook.eu1.make.com
  1. Click Save

Nginx Configuration

On Apache, the Agent API works out of the box. On Nginx (Forge, RunCloud, Ploi), routing works automatically via the Studio router fallback — no rewrite rule is required. You only need one addition:

Forward the Authorization header (required)

Add this inside your location ~ \.php$ block:

fastcgi_param HTTP_AUTHORIZATION $http_authorization;

Without this line, Nginx strips the Authorization header and all Agent API requests fail with 401.

Route Agent API requests directly (optional)

For direct routing — bypassing the Studio router for better performance — add this before the location ~ \.php$ block:

location ~ ^/_studio/api/agent/v1/(?!.*\.php)(.*)$ {
    rewrite ^/_studio/api/agent/v1/(.*)$ /_studio/api/agent/v1/router.php?_path=$1&$args last;
}

This is a performance optimization. The Studio router fallback handles the same routing automatically if this rule is not present.

See Nginx Configuration for the complete config example.


Examples

List all pages

curl -H "Authorization: Bearer vxs_abc123..." \
     https://yourdomain.com/_studio/api/agent/v1/pages

Create a page

curl -X POST \
     -H "Authorization: Bearer vxs_abc123..." \
     -H "Content-Type: application/json" \
     -d '{"title": "About Us", "slug": "about", "content": "<?php ..."}' \
     https://yourdomain.com/_studio/api/agent/v1/pages

Compile and publish

# Recompile Tailwind CSS
curl -X POST \
     -H "Authorization: Bearer vxs_abc123..." \
     https://yourdomain.com/_studio/api/agent/v1/compile

# Publish to production
curl -X POST \
     -H "Authorization: Bearer vxs_abc123..." \
     https://yourdomain.com/_studio/api/agent/v1/publish

Upload an image

curl -X POST \
     -H "Authorization: Bearer vxs_abc123..." \
     -F "[email protected]" \
     -F "category=images" \
     https://yourdomain.com/_studio/api/agent/v1/assets

Invoke a tool

curl -X POST \
     -H "Authorization: Bearer vxs_abc123..." \
     -H "Content-Type: application/json" \
     -d '{"name": "get_business_info", "arguments": {}}' \
     https://yourdomain.com/_studio/api/agent/v1/tools/invoke

Logging & Debugging

Every Agent API request is logged to help you diagnose issues, especially on servers you can't access directly.

Log Location

Logs are stored in _studio/logs/ as daily JSON-line files:

_studio/logs/2026-03-16.log
_studio/logs/2026-03-15.log

Each line is valid JSON, making logs easy to parse with jq, grep, or any log viewer. Log files are automatically rotated — files older than 30 days are pruned.

Log Format

Every Agent API log entry uses the agent-api channel and includes:

{
  "ts": "2026-03-16T14:23:01.123Z",
  "level": "INFO",
  "ch": "agent-api",
  "rid": "a1b2c3d4",
  "msg": "POST /pages",
  "ctx": {
    "handler": "pages.php",
    "scope": "pages:write",
    "key_label": "My Website Automation",
    "key_role": "agent",
    "key_pre": "vxs_61b90f0b",
    "ip": "192.168.1.100"
  }
}
Field Description
ts UTC timestamp (millisecond precision)
level INFO, WARNING, or ERROR
ch Always agent-api for Agent API entries
rid Request ID — same for all log entries within a single request
msg What happened — e.g. POST /pages, Page created, Auth failed: invalid_key
ctx Structured metadata — key label, role, IP, error details

What Gets Logged

Event Level Example message
Request dispatched INFO POST /pages
Page created/updated/deleted INFO Page created, Page updated, Page deleted
Site published INFO Site published
CSS compiled INFO CSS compiled
Settings updated INFO Settings updated
Asset uploaded INFO Asset uploaded
Tool invoked INFO Tool invoked: data, Tool invoked: form, Tool invoked: action
Auth failure WARNING Auth failed: invalid_key, Auth failed: rate_limited
Scope denied WARNING Scope denied
Route not found WARNING Route not found
Page not found WARNING Page not found, Page not found for update
Asset validation WARNING Asset upload: no file, Asset upload: file too large, Asset upload: invalid category, Asset blocked extension
Settings rejected WARNING Settings update: all keys rejected
Tool validation WARNING Tool invoke: missing name, Tool invoke: form not found, Tool invoke: form validation failed, Tool invoke: action failed
Feature/demo blocked INFO Request blocked: feature disabled, Request blocked: demo mode
Operation error ERROR Page create failed, Publish failed, Asset file save failed, CSS compile failed

Filtering Logs

Grep for Agent API entries only:

grep '"ch":"agent-api"' _studio/logs/2026-03-16.log

Filter by level:

grep '"ch":"agent-api"' _studio/logs/2026-03-16.log | grep '"level":"ERROR"'

Filter by key label:

grep '"ch":"agent-api"' _studio/logs/2026-03-16.log | grep '"key_label":"My Website Automation"'

Trace a complete request by request ID:

grep '"rid":"a1b2c3d4"' _studio/logs/2026-03-16.log | jq .

Sending Logs for Support

If you encounter an issue with the Agent API:

  1. Note the approximate time the error occurred
  2. Find the matching log file in _studio/logs/ (named by date)
  3. Filter for agent-api entries around that time
  4. Send the relevant log lines to support — they contain:
    • Request ID (rid) to correlate all entries from a single request
    • Key label and role to identify which integration failed
    • IP address and user agent for network-level context
    • Error details: validation warnings include the specific error code and rejected values; exception errors include the exception class, file, line number, and a truncated stack trace

Security note: Log files contain key prefixes (first 12 characters) but never the full API key. IP addresses and key labels are included for debugging. Log files are protected from web access by .htaccess and should not be publicly accessible.


Related

Ready to build?

One-time purchase. Self-hosted. Own every file forever.

Get VoxelSite