VayuAPI — API Keys, Permissions & Rate Limits
Status: Authoritative
Last reviewed: 2026-10-17, against VayuPress 3.18.7
Overview
Every programmatic caller of the VayuPress API — a script, a CI job, an
integration, an AI agent, a VCB extension — authenticates with an API key
that carries a fine-grained grant set. A key can do exactly what it was
granted, nothing more. This page is the reference the API's own error
responses link to (the docs field of an error body points here).
Related: /docs/compatibility/vcb (extension contracts) · /docs/API-REFERENCE (endpoints) · /docs/adr/ADR-0134-vayuapi-fine-grained-keys (design record).
Base URL
Call the API at https://<your-domain>/api/v1/….
Behind a proxy that shows a bot "challenge" page (e.g. Cloudflare Bot Fight Mode /
managed challenge), a script, CI job or AI agent cannot solve it, so those
calls fail. For that case VayuPress can serve the API on a dedicated, proxy-off
host: point api.<your-domain> straight at your server (proxy off / DNS only),
set VAYUOS_API_HOST=api.<your-domain>, and re-run the installer. It provisions a
hardened host that exposes only /api and /health (the admin console is
not reachable there), so clients use https://api.<your-domain>/api/v1/…. The
recommended base URL is shown on VayuOS → API Keys. VayuShield still guards the
origin and every call is key-authenticated + rate-limited, so the direct host has a
minimal, safe surface. See INSTALLATION.md → Direct API host.
Authenticating
Send the key with either header on any protected route:
X-API-Key: vp_xxxxxxxxxxxxxxxx
Authorization: Bearer vp_xxxxxxxxxxxxxxxx
Keys are shown once, at creation or rotation. Only a SHA-256 hash is stored; a lost key cannot be recovered, only rotated.
The capability model
A capability is written section:action. A key holds a set of capabilities;
every protected route requires one, and the check is deny by default — a
route's capability missing from the key means 403, and a scoped key hitting
a route outside the mapped table is refused outright.
Twelve sections × six actions — any pair is a grantable capability:
| Vocabulary | Values |
|---|---|
| Sections | posts, themes, design, plugins, domains, analytics, media, comments, members, mail, settings, backup |
| Actions | read, write, delete, apply, install, export |
Examples: posts:write (create/edit articles), themes:apply (switch the
active theme), analytics:read, backup:export. Each route requires the
capability matching its intent; a 403 names the exact section:action you
are missing, so the error itself tells you what to grant. Granting a pair no
route uses yet is harmless — it simply matches nothing until such a route
exists.
Wildcards exist for operators only: section:* grants a section's every
action; *:* is a full-access (superuser) key. Extension manifests may not
declare wildcards — the VCB validator refuses them.
Exactly two credentials are implicit superusers: the static bootstrap
API_KEY environment credential and the internal system key. Every key minted
in the console is grant-limited.
Key lifecycle
Manage keys in VayuOS → API Keys (owner-scoped; the full key is shown once in a copy-once banner):
| Operation | Effect |
|---|---|
| Create | Pick a label, tick capabilities in the 12 × 6 grid (or Full access), optionally set an expiry and a per-minute budget. |
| Rotate | New secret, same grants; the old value stops working immediately. |
| Deactivate / Activate | Reversible disable — the key refuses to authenticate until re-activated. |
| Revoke | Permanent disable; the audit row is kept. |
| Delete | Removes the key. Keys expired more than 30 days are swept automatically. |
| Expiry | Enforced exactly — an expired key is refused on the next request, never lagged by caching. |
For VCB extensions, the manifest's api_permissions list is the provisioning
contract: mint the key with exactly those boxes ticked
(see /docs/compatibility/vcb).
Rate limits
Each external key has its own fixed-window budget, keyed by the key's identity (not the caller's IP — rotating addresses does not escape it):
- Default: 600 requests/minute, or what the operator sets for every key
on API keys › Limits: a number from 1 to 100,000, or unlimited.
VAYU_API_RATE_PER_MINchanges the built-in 600. - Per key: the default, a number of its own, or unlimited, set when the key is made or later in its Manage sheet. An MCP connector is a key, and its budget is set the same way.
- Over budget →
429withRetry-After: 60and this page in the error body. - The internal system key is exempt.
- An authenticated call is held to its key's budget alone: no per-address
limit stands behind it. The limits that guard the server from strangers
(sign-in attempts, public endpoints, VayuShield) are not lifted by a key's
setting. Many posts at once still go best through
POST /api/v1/articles/bulk(up to 1,000 a request).
Handle 429 by honouring Retry-After — do not retry in a tight loop.
Errors
Error bodies are JSON, under one error object:
{
"error": {
"code": "forbidden",
"message": "this API key does not hold the required capability: posts:write",
"request_id": "c028beea80ee1ebe",
"docs": "/docs/compatibility/vayuapi"
}
}
request_id matches the X-Request-Id header and the server's log line.
| Status | Code | Meaning |
|---|---|---|
401 |
unauthorized |
Missing or invalid key. |
403 |
forbidden |
Valid key, missing capability — the message names the section:action you need. |
429 |
rate_limited |
Per-key budget exhausted; honour Retry-After. |
Audit
Every key-authenticated call is appended to the tamper-evident audit log as
api.call | key:<id> | METHOD /path | status, giving the operator a per-key
usage trail that never leaves the box. Design your integrations assuming their
traffic is attributable.