VayuPress Docs
Docs / Compatibility

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_MIN changes 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 → 429 with Retry-After: 60 and 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.