Skip to content

Agent API

Connect external agents and scripts to VoxelSite with authenticated REST endpoints. Check roles, scopes and beta limits before enabling access.

Updated October 23, 2026 16 min read

The Agent API is VoxelSite’s authenticated REST interface for scripts and integrations. It remains beta. Test an integration on a copy before granting write or publish access.

Read the installed OpenAPI specification at /_studio/api/agent/v1/schema. Current writes use stable identities and captured versions; older request examples without those fields are not valid for 1.33.0.

Agent API vs MCP

Interface Endpoint Purpose and access
Agent REST API /_studio/api/agent/v1/ Site management through HTTP, using an enabled REST credential and its scopes
Studio MCP /_studio/mcp.php Private authoring, exact reviews, lifecycle and administration through scoped account-bound connections
Public MCP /mcp.php Published business information and enabled visitor interactions; no private authoring access

REST and Studio MCP credentials have separate audiences. Do not send one to the other endpoint. See Studio MCP overview for external-client setup.

Enabling the Agent API

The owner enables Settings → API Access → Enable Agent API, then saves API settings. Demo mode blocks access.

Create a REST key with the role and scopes the integration needs. Save the secret once in a private environment variable. Keys use the vxs_ prefix and are stored as hashes. Revocation prevents further use.

Existing keys need a current-account binding. In Settings → Connections, use Connect existing REST keys to an account when prompted. Choose the intended account explicitly. The credential’s audience and scopes remain unchanged; its account’s current role is an additional permission ceiling. An unbound key cannot be used as an implicit owner.

Roles and scopes

Key role Typical default scope ceiling
Agent Pages, assets, compile, publish, submissions, safe settings reads and tools
Editor Pages, assets, compile, submissions and tools
Viewer Read-only pages, assets, submissions and safe settings

The owner controls the granted scopes. Provider-backed prompt:execute is a separate opt-in grant and is unavailable to viewer keys. It also needs exec() and PHP CLI; see Prompt execution.

Authentication

Send the credential in the Authorization header over HTTPS. Example:

curl -H "Authorization: Bearer $VOXELSITE_API_KEY" \
  https://example.com/_studio/api/agent/v1/pages

Missing, malformed or invalid credentials return HTTP 401. Permission failures return 403. Current account, role and scope checks also apply to retained retries. Authentication and rate-limit responses can include X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset.

Versioned writes and retries

Read the current draft or record before writing. Keep its version and hash with the request you intend to send.

  • Use a stable operation_id for page, compile, publish and settings writes.
  • Use request_id for asset uploads and business-tool mutations where required by their contract.
  • Reuse the same identity and original input after a lost response. A new identity requests a new operation.
  • If an operation returns applying, use its returned checkpoint to continue the original request.
  • A completed replay returns the retained result. It does not create another revision or publication.
  • A source conflict requires a fresh read and a new intentional edit, not a guessed version or forced overwrite.

Page and compile operations share the private draft with Studio and Studio MCP. Draft CSS, scripts and mutable assets stay off the published site until publication. Business submissions are immediate live records. See Site lifecycle and Business workspaces.

Endpoint reference

Prefix these paths with /_studio/api/agent/v1. The installed schema provides the version-specific details.

Pages

Method Path Scope Result
GET /pages pages:read Draft page list, source hashes and draft version
GET /pages/:slug pages:read Page source, content hash and draft version
POST /pages pages:write Create a draft page
PUT /pages/:slug pages:write Update source, metadata or supported route fields
DELETE /pages/:slug pages:write Delete a page with supported reference updates

Use page and per_page for REST listing. Pass the captured draft_version when continuing the inventory. Read returns data.page.content_sha256 and data.draft_version.

All page writes require operation_id, base_draft_version and expected_content_sha256. Creation uses explicit null for the expected hash and also requires slug, title and content. Use a canonical lowercase slug with hyphens; it is validated rather than silently rewritten. Updates and deletion require the hash returned by the page read. A deletion can include replacementHref.

Example creation body, after replacing the illustrative version with the value you read:

{
  "operation_id": "create-about-unique-request",
  "base_draft_version": "draft:12",
  "expected_content_sha256": null,
  "slug": "about",
  "title": "About us",
  "content": "<?php $page = ['title' => 'About us']; include __DIR__ . '/_partials/header.php'; ?><h1>About us</h1><?php include __DIR__ . '/_partials/footer.php'; ?>"
}

The response includes a retained operation, resulting draft version and affected paths. Inspect operation.status; HTTP success alone does not mean bounded work has finished. Supported reference updates are reviewed by the shared service. Unsafe or ambiguous propagation can be refused.

Build & publish

Method Path Scope Request
POST /compile compile:trigger operation_id, captured base_draft_version, optional returned checkpoint
POST /publish publish:trigger operation_id, optional create_snapshot, optional returned checkpoint

Compile changes private draft CSS. REST publication is an explicit command to seal and publish the current draft. For a separate prepare, inspect and apply interaction, use the Studio MCP publication tools.

Publication returns HTTP 202 while applying and HTTP 200 when succeeded. The data.operation object carries status and checkpoint. Re-submit the same operation ID with the returned checkpoint to continue. Repeating the ID without a checkpoint reads its retained result. create_snapshot: false does not disable the required recovery capture.

Example publish body:

{"operation_id":"publish-reviewed-site-unique-request","create_snapshot":true}

A stale candidate, changed live file or unresolved recovery blocks publication. It does not authorize overwriting unrelated content.

Settings

Method Path Scope Result
GET /settings settings:read Safe settings, group versions and group keys
PUT /settings settings:write Versioned change to allowed site settings

Read returns data.settings, data.versions and data.group_keys. The writable groups are site identity, navigation and retention. PUT requires operation_id, a typed values object and the captured versions for the groups changed. Read the current response before assembling the request.

REST settings cannot write provider configuration, secrets, mail settings or the favicon. Use the owner controls or explicitly approved Studio MCP administration. Unknown fields are refused; they are not silently ignored. The result includes the retained operation and changed keys.

Submissions

GET /submissions requires submissions:read. Choose source=form, source=action or source=all. The /submissions/forms and /submissions/actions routes select their corresponding source.

Supported filters include form_id or entity_id, status, search, date_from, date_to and record_source. Use limit or per_page from 1 to 100. Continue with the returned next_cursor, keeping the same filter. Offset page numbers beyond the first page are refused.

The result contains submissions, next_cursor and consistency information. Form and Action records retain their own storage and source attribution. Website snapshots do not restore these records.

Assets

GET /assets requires assets:read and returns private draft assets. Continue listing with the captured draft_version.

POST /assets requires assets:write and multipart form data:

Field Meaning
file File, up to 10 MiB
request_id Stable identity for this exact upload
base_draft_version Version captured from the asset list
category Optional images, fonts, media or files
path Optional allowed target path

The adapter validates the file type and stages it privately before applying the draft change. Edit CSS and JavaScript through authoring tools. Uploading a file does not publish it. The response includes the path, review and retained operation.

Tools

GET /tools lists the business tools allowed for the credential. POST /tools/invoke uses the closed {name, arguments} envelope, plus the mutation fields returned by discovery.

Tool Purpose
get_business_info Published site.json information
get_menu Published menu, with optional category filter
get_services Published services
get_faq Published FAQ, with optional query filter
list_forms Published form schemas
get_form_schema A published form and its submission version
submit_form Create a real form record

Active Actions add their own tools. Discovery provides each Action’s ID, version and required request fields. These reads use published data, never private authoring memory or a draft fallback.

For submit_form, pass the schema’s version and a stable request_id at the envelope’s top level. Action mutations also require the discovered action_id. Reuse the same identity and exact input after a lost response. Notification sends need explicit mail access; do not interpret record storage as email delivery.

Example form submission envelope, using the actual version returned by get_form_schema:

{
  "name": "submit_form",
  "version": "REPLACE_WITH_RETURNED_VERSION",
  "request_id": "contact-unique-request",
  "arguments": {
    "form_id": "contact",
    "data": {"name": "Example visitor", "email": "[email protected]", "message": "Please contact me."}
  }
}

Prompt execution

This beta endpoint calls the configured built-in provider and can incur provider charges. It is separate from provider-free Studio MCP authoring. Prompt execution shares the owner’s AI Router settings with Studio. Routing can select the writing model and prompt recipe; a routing failure uses the default generation path. Agent clients cannot change Router settings or read the TypeSafe key. Direct file and publish endpoints do not pass through the router.

Beta: Test prompt execution on a copy of the site. Check terminal status and modified files before submitting another prompt.

Run AI prompts programmatically through the Agent API. Prompts are executed asynchronously: you submit a prompt, receive a poll URL, and check back for results.

Prerequisites

Requirement Details
Provider Configure built-in generation in Studio; an external-client login does not configure this endpoint.
Server exec() must be enabled (not in disable_functions), and a working PHP command-line executable must be available to the web server. Hosting that blocks either requirement cannot run Agent prompts.
Scope The API key must have the prompt:execute scope. This is opt-in - check AI Prompt Execution when generating the key.
Role Only owner, editor, and agent keys are eligible. Viewer keys cannot get prompt:execute because the prompt engine has full workspace write access.

VoxelSite verifies the PHP executable before creating a conversation or queued job. A failed check returns 503 php_cli_unavailable; disabled exec() returns 503 exec_unavailable. Accepted prompts use the same saved output ceiling and model limits as Studio generation.

Async flow

sequenceDiagram
    participant Agent as Your Agent / Tool
    participant API as Agent API
    participant Worker as prompt-runner.php

    Agent->>API: POST /prompt {prompt, page_scope?}
    API->>API: Verify PHP command-line executable
    API-->>Agent: 202 Accepted {id, status: "queued", poll_url}
    API->>Worker: spawn background worker
    Worker->>Worker: PromptEngine::execute()

    loop Poll every 2-5 seconds
        Agent->>API: GET /prompt/{id}
        API-->>Agent: {status, files_modified?, tokens?}
    end

    Note over Agent: status: queued → streaming → success / partial / error
Method Path Scope Description
POST /prompt prompt:execute Queue a prompt for execution
GET /prompt/:id prompt:execute Poll prompt status and result

POST /prompt - queue

Field Type Required Description
prompt string ✅ The instruction for the AI (e.g. "Create an About page with our team info")
action_type string - Prompt type. Default: free_prompt. Also: edit_page, change_design, add_section, section_edit, optimize_aeo, inline_edit
action_data object - Action-specific parameters (e.g. target element for inline_edit)
page_scope string - Focus context on a page slug; this does not restrict all writes to that page
continue_from integer - A previous prompt's id to continue the same conversation. Must belong to the same API key.

Returns: 202 with:

{
  "data": {
    "id": 42,
    "conversation_id": "a1b2c3d4-...",
    "status": "queued",
    "poll_url": "/_studio/api/agent/v1/prompt/42"
  }
}

GET /prompt/:id - poll

Returns: 200 with prompt status and - for terminal statuses - result details:

Field Type Present Description
id integer always Prompt log ID
conversation_id string always UUID of the conversation
action_type string always The action type (free_prompt, etc.)
prompt string always The original prompt text
status string always queued, streaming, success, partial, or error
status_message string always Human-readable status (e.g. "Waiting for worker...", "Generating...")
ai_provider string always AI provider used (e.g. claude, openai)
ai_model string always Model used (e.g. claude-sonnet-4-20250514). Shows pending while queued.
created_at string always ISO timestamp of when the prompt was queued
files_modified array terminal List of file paths modified by the AI
tokens object terminal { input: int, output: int } - token counts
cost_estimate float|null terminal Estimated cost in USD
duration_ms integer|null terminal Execution time in milliseconds
error_message string|null terminal Error details (for error or partial status)

"terminal" means the field is only present when status is success, partial, or error.

Status lifecycle

Status Meaning
queued Prompt is waiting for the background worker to start
streaming The AI is actively generating - worker is running
success Prompt completed successfully - check files_modified and tokens
partial Incomplete execution, including usable output that reached the model's limit - check files_modified and error_message
error Prompt failed, including an empty response or an output limit reached before usable changes - check error_message

Stale detection:

  • A prompt stuck in queued for 60 seconds (worker failed to spawn) is marked error
  • A prompt stuck in streaming for 300 seconds (worker died) is marked partial

Error codes

HTTP Code When
422 validation_error Empty or missing prompt, invalid action_type, or invalid action_data
403 forbidden continue_from references a prompt from a different API key
404 not_found Prompt ID doesn't exist or belongs to another key
503 exec_unavailable exec() is disabled on this server
503 php_cli_unavailable No working PHP command-line executable could be verified; no conversation or job was created

Scope and isolation

  • Shared private draft - prompts use the same private website files as Studio. Review the changed-file list; page scope is a context hint.
  • Per-key isolation - GET /prompt/:id returns 404 for prompts created by a different key. continue_from enforces the same key.

Response format

Successful REST responses use a data envelope. Errors use error.code, error.message and optional error.details. Mutation responses can include a retained operation even when further work or recovery is required.

Status or code What to do
HTTP 401 Check the credential and Authorization forwarding
HTTP 403 / permission_denied Check current account, role, audience and scopes
credential_migration_required Ask the owner to bind the REST credential to its intended account
invalid_schema Use the installed contract and required version/identity fields
content_conflict Read current source and prepare a new intentional request
idempotency_conflict Do not reuse an identity for different input
operation_pending Inspect or continue the retained operation
recovery_required Follow recovery before another website write
HTTP 429 Respect the returned rate-limit reset
host_capability_unavailable Check the host requirement reported in the error

Tool calling & schema

GET /schema is public and describes the installed REST routes. Keep the HTTP method, path, parameters and request body together when mapping an operation into an integration. Studio MCP uses its own tool discovery and schema; the two protocols are not interchangeable.

The schema’s info.x-capabilities.prompt_execution.available flag checks exec(). PHP CLI is checked when a prompt is submitted. Do not infer prompt readiness from that flag alone.

Scopes reference

Scope Grants
pages:read, pages:write Draft page reads and writes
assets:read, assets:write Private asset reads and uploads
compile:trigger Draft Tailwind compilation
publish:trigger Explicit publication
settings:read, settings:write Safe reads and allowed versioned settings changes
submissions:read Business inbox reads
tools:invoke Business tool discovery and invocation within account permissions
prompt:execute Opt-in provider-backed prompt jobs

CORS origins

The owner can restrict browser origins under Settings → API Access → Allowed Origins, one origin per line. * permits all origins. CORS is a browser policy; it does not replace credential or scope checks.

Nginx configuration

Forward Authorization to PHP-FPM:

fastcgi_param HTTP_AUTHORIZATION $http_authorization;

Use the Nginx guide for private-directory protection and API rewrites. If the Studio API router already receives /agent/v1/ requests, it forwards them to the REST router. A generic PHP host still needs its web-server routes configured.

Read-only example

import os
import requests

response = requests.get(
    "https://example.com/_studio/api/agent/v1/pages",
    headers={"Authorization": f"Bearer {os.environ['VOXELSITE_API_KEY']}"},
    params={"page": 1, "per_page": 50},
    timeout=30,
)
response.raise_for_status()
print(response.json())

Inspect errors and versions before creating a later write request. Store credentials outside source files.

Logging & debugging

Use Agent API logs to trace dispatched requests and authentication failures. Download them through Settings when you lack shell access.

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.


Found a mistake in these instructions? Open an issue.

Download VoxelSite