6404 lines
207 KiB
YAML
6404 lines
207 KiB
YAML
openapi: 3.0.3
|
|
|
|
info:
|
|
title: Grav CMS API
|
|
description: |
|
|
RESTful API for managing Grav CMS content, media, configuration, users, and
|
|
system operations.
|
|
|
|
## Authentication
|
|
|
|
The API supports three authentication methods. All three provide the same
|
|
level of access — the authenticated user's permissions apply regardless of
|
|
method. Every request (except `/auth/*` endpoints) must include one of:
|
|
|
|
| Method | Header / mechanism | Best for |
|
|
|---|---|---|
|
|
| **API key** | `X-API-Key: grav_...` | CLI tools, MCP servers, server-to-server, automation |
|
|
| **JWT token** (recommended) | `X-API-Token: <token>` | SPAs, mobile apps, admin-next plugin bundles |
|
|
| **JWT Bearer token** (alt) | `Authorization: Bearer <token>` | Standard HTTP clients on hosts that don't strip the header |
|
|
| **Session cookie** | `grav-site` cookie | Admin panel extensions |
|
|
|
|
> Prefer `X-API-Token` over `Authorization: Bearer` when portability matters:
|
|
> PHP under FastCGI / CGI / PHP-FPM (notably MAMP's `mod_fastcgi`) silently
|
|
> strips `Authorization` before it reaches the application. A custom `X-*`
|
|
> header bypasses this entirely. The server accepts either transport for the
|
|
> same JWT.
|
|
|
|
**API keys** are long-lived credentials that don't expire by default. Generate
|
|
them via `bin/plugin api keys:generate` or the admin panel. Best for any
|
|
server-side or automated integration.
|
|
|
|
**JWT tokens** are short-lived (1 hour default). Obtain them by posting
|
|
credentials to `/auth/token`, then refresh before expiry via `/auth/refresh`.
|
|
The refresh flow uses token rotation — each refresh revokes the previous
|
|
refresh token. Best for client-side apps where long-lived secrets shouldn't
|
|
be stored.
|
|
|
|
## Response Envelope
|
|
|
|
All successful responses use a standard JSON envelope:
|
|
|
|
```json
|
|
{
|
|
"data": { ... },
|
|
"meta": { ... },
|
|
"links": { ... }
|
|
}
|
|
```
|
|
|
|
`meta` and `links` are present only on paginated responses.
|
|
|
|
## Error Format (RFC 7807)
|
|
|
|
Error responses use `application/problem+json`:
|
|
|
|
```json
|
|
{
|
|
"status": 422,
|
|
"title": "Unprocessable Entity",
|
|
"detail": "Missing required fields: title",
|
|
"errors": [
|
|
{ "field": "title", "message": "The 'title' field is required." }
|
|
]
|
|
}
|
|
```
|
|
|
|
## Environments
|
|
|
|
Send the optional `X-Grav-Environment` header to target a specific Grav
|
|
environment. If omitted, defaults to the auto-detected environment (from
|
|
hostname). Use `GET /system/environments` to discover available environments.
|
|
|
|
## Rate Limiting
|
|
|
|
When rate limiting is enabled, every response includes:
|
|
|
|
- `X-RateLimit-Limit` -- maximum requests per window
|
|
- `X-RateLimit-Remaining` -- requests remaining
|
|
- `X-RateLimit-Reset` -- UTC epoch when the window resets
|
|
|
|
## Optimistic Concurrency (ETags)
|
|
|
|
Single-resource GET responses include an `ETag` header. For PATCH requests,
|
|
send the same value in `If-Match` to detect mid-air collisions. A `409
|
|
Conflict` response is returned when the resource has changed since the last
|
|
GET.
|
|
version: 1.0.0-beta.1
|
|
license:
|
|
name: MIT
|
|
url: https://opensource.org/licenses/MIT
|
|
contact:
|
|
name: Team Grav
|
|
url: https://getgrav.org
|
|
email: devs@getgrav.org
|
|
|
|
servers:
|
|
- url: "{baseUrl}/api/v1"
|
|
description: Default Grav CMS API server
|
|
variables:
|
|
baseUrl:
|
|
default: ""
|
|
description: >
|
|
The base URL of the Grav installation. Leave empty when using
|
|
relative paths, or set to a full origin such as
|
|
https://example.com.
|
|
|
|
tags:
|
|
- name: Auth
|
|
description: JWT token management -- obtain, refresh, and revoke tokens.
|
|
- name: Pages
|
|
description: Create, read, update, delete, move, and copy Grav pages.
|
|
- name: Media
|
|
description: Manage media files attached to pages or the site-level images folder.
|
|
- name: Config
|
|
description: Read and update Grav configuration (system, site, plugins, themes).
|
|
- name: Users
|
|
description: User account and API key management.
|
|
- name: System
|
|
description: System information, cache management, logs, and backups.
|
|
- name: Languages
|
|
description: Site language configuration and page translations.
|
|
- name: GPM
|
|
description: Package management -- install, remove, update plugins, themes, and Grav core.
|
|
- name: Scheduler
|
|
description: Job scheduling, status, and execution history.
|
|
- name: Sidebar
|
|
description: Admin sidebar navigation items contributed by plugins.
|
|
- name: Dashboard
|
|
description: Notifications, news feed, and statistics.
|
|
- name: Webhooks
|
|
description: Outbound webhook configuration and delivery management.
|
|
- name: Blueprints
|
|
description: Form schema definitions for pages, plugins, themes, and system configuration. Blueprints define the fields, types, validation, and layout used to render editing interfaces.
|
|
- name: Data
|
|
description: Resolve blueprint data-options@ directives and other dynamic data lookups.
|
|
- name: Thumbnails
|
|
description: Serve cached image thumbnails.
|
|
|
|
security:
|
|
- apiToken: []
|
|
- apiKey: []
|
|
- bearerAuth: []
|
|
- sessionAuth: []
|
|
|
|
paths:
|
|
# ---------------------------------------------------------------------------
|
|
# Auth
|
|
# ---------------------------------------------------------------------------
|
|
/auth/token:
|
|
post:
|
|
operationId: createToken
|
|
tags: [Auth]
|
|
summary: Obtain a JWT token pair
|
|
description: |
|
|
Authenticate with username and password to receive an access token and a
|
|
refresh token. The access token is short-lived; use the refresh token to
|
|
obtain a new pair without re-authenticating.
|
|
security: []
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
required: [username, password]
|
|
properties:
|
|
username:
|
|
type: string
|
|
example: admin
|
|
password:
|
|
type: string
|
|
format: password
|
|
example: Pa$$w0rd!
|
|
example:
|
|
username: admin
|
|
password: Pa$$w0rd!
|
|
responses:
|
|
"200":
|
|
description: Token pair returned successfully.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
properties:
|
|
data:
|
|
$ref: "#/components/schemas/TokenResponse"
|
|
example:
|
|
data:
|
|
access_token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
|
|
refresh_token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
|
|
token_type: Bearer
|
|
expires_in: 3600
|
|
"401":
|
|
$ref: "#/components/responses/Unauthorized"
|
|
"403":
|
|
$ref: "#/components/responses/Forbidden"
|
|
"422":
|
|
$ref: "#/components/responses/ValidationError"
|
|
"429":
|
|
$ref: "#/components/responses/TooManyRequests"
|
|
|
|
/auth/refresh:
|
|
post:
|
|
operationId: refreshToken
|
|
tags: [Auth]
|
|
summary: Refresh a JWT token pair
|
|
description: |
|
|
Exchange a valid refresh token for a new access/refresh pair. The
|
|
provided refresh token is automatically revoked (token rotation).
|
|
security: []
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
required: [refresh_token]
|
|
properties:
|
|
refresh_token:
|
|
type: string
|
|
description: The refresh token obtained from `/auth/token` or a previous refresh.
|
|
example:
|
|
refresh_token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
|
|
responses:
|
|
"200":
|
|
description: New token pair returned successfully.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
properties:
|
|
data:
|
|
$ref: "#/components/schemas/TokenResponse"
|
|
"401":
|
|
$ref: "#/components/responses/Unauthorized"
|
|
"403":
|
|
$ref: "#/components/responses/Forbidden"
|
|
"422":
|
|
$ref: "#/components/responses/ValidationError"
|
|
"429":
|
|
$ref: "#/components/responses/TooManyRequests"
|
|
|
|
/auth/revoke:
|
|
post:
|
|
operationId: revokeToken
|
|
tags: [Auth]
|
|
summary: Revoke a refresh token
|
|
description: |
|
|
Explicitly revoke a refresh token so it can no longer be used. This is
|
|
the recommended way to implement logout on the client side.
|
|
security: []
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
required: [refresh_token]
|
|
properties:
|
|
refresh_token:
|
|
type: string
|
|
example:
|
|
refresh_token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
|
|
responses:
|
|
"204":
|
|
description: Token revoked successfully. No content returned.
|
|
"422":
|
|
$ref: "#/components/responses/ValidationError"
|
|
"429":
|
|
$ref: "#/components/responses/TooManyRequests"
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Ping
|
|
# ---------------------------------------------------------------------------
|
|
/ping:
|
|
get:
|
|
operationId: ping
|
|
tags: [System]
|
|
summary: Health check / keep-alive
|
|
description: |
|
|
Simple health check endpoint. Returns a static response to confirm the
|
|
API is reachable and the caller is authenticated.
|
|
responses:
|
|
"200":
|
|
description: API is reachable.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
properties:
|
|
data:
|
|
type: object
|
|
properties:
|
|
pong:
|
|
type: boolean
|
|
example: true
|
|
example:
|
|
data:
|
|
pong: true
|
|
"401":
|
|
$ref: "#/components/responses/Unauthorized"
|
|
"403":
|
|
$ref: "#/components/responses/Forbidden"
|
|
"429":
|
|
$ref: "#/components/responses/TooManyRequests"
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Pages
|
|
# ---------------------------------------------------------------------------
|
|
/pages:
|
|
get:
|
|
operationId: listPages
|
|
tags: [Pages]
|
|
summary: List pages
|
|
description: |
|
|
Retrieve a paginated list of all pages. Supports filtering by
|
|
published state, template, routable/visible flags, and parent route.
|
|
Results can be sorted by date, title, slug, modified time, or order.
|
|
|
|
The listing uses a lightweight serialization that excludes content,
|
|
media, and children for performance.
|
|
parameters:
|
|
- $ref: "#/components/parameters/page"
|
|
- $ref: "#/components/parameters/per_page"
|
|
- $ref: "#/components/parameters/lang"
|
|
- name: sort
|
|
in: query
|
|
description: |
|
|
Field to sort by. When omitted the default sort is `date` descending.
|
|
schema:
|
|
type: string
|
|
enum: [date, title, slug, modified, order]
|
|
- name: order
|
|
in: query
|
|
description: Sort direction.
|
|
schema:
|
|
type: string
|
|
enum: [asc, desc]
|
|
default: asc
|
|
- name: published
|
|
in: query
|
|
description: Filter by published state.
|
|
schema:
|
|
type: boolean
|
|
- name: template
|
|
in: query
|
|
description: Filter by page template name (e.g. `blog`, `default`).
|
|
schema:
|
|
type: string
|
|
example: blog
|
|
- name: routable
|
|
in: query
|
|
description: Filter by routable flag.
|
|
schema:
|
|
type: boolean
|
|
- name: visible
|
|
in: query
|
|
description: Filter by visible flag.
|
|
schema:
|
|
type: boolean
|
|
- name: parent
|
|
in: query
|
|
description: |
|
|
Filter pages whose route starts with this parent path (e.g. `/blog`
|
|
returns `/blog/post-1`, `/blog/post-2`, etc.). Returns all descendants.
|
|
schema:
|
|
type: string
|
|
- name: children_of
|
|
in: query
|
|
description: |
|
|
Return only **direct children** of the given route (one level deep).
|
|
Unlike `parent`, this does not return deeper descendants.
|
|
Use `children_of=/` for root-level pages. Ideal for lazy-loading
|
|
tree views and miller column interfaces.
|
|
schema:
|
|
type: string
|
|
example: /blog
|
|
- name: root
|
|
in: query
|
|
description: Filter to top-level (root) pages only.
|
|
schema:
|
|
type: boolean
|
|
example: /blog
|
|
responses:
|
|
"200":
|
|
description: Paginated list of pages.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
allOf:
|
|
- $ref: "#/components/schemas/PaginatedEnvelope"
|
|
- type: object
|
|
properties:
|
|
data:
|
|
type: array
|
|
items:
|
|
$ref: "#/components/schemas/PageSummary"
|
|
example:
|
|
data:
|
|
- route: /blog/my-first-post
|
|
slug: my-first-post
|
|
title: My First Post
|
|
template: blog
|
|
header:
|
|
title: My First Post
|
|
taxonomy:
|
|
category: [blog]
|
|
tag: [intro, grav]
|
|
taxonomy:
|
|
category: [blog]
|
|
tag: [intro, grav]
|
|
published: true
|
|
visible: true
|
|
routable: true
|
|
date: "2026-03-20T12:00:00+00:00"
|
|
modified: "2026-03-22T09:30:00+00:00"
|
|
order: 1
|
|
has_children: true
|
|
meta:
|
|
pagination:
|
|
page: 1
|
|
per_page: 20
|
|
total: 42
|
|
total_pages: 3
|
|
links:
|
|
self: "/api/v1/pages?page=1&per_page=20"
|
|
next: "/api/v1/pages?page=2&per_page=20"
|
|
last: "/api/v1/pages?page=3&per_page=20"
|
|
"401":
|
|
$ref: "#/components/responses/Unauthorized"
|
|
"403":
|
|
$ref: "#/components/responses/Forbidden"
|
|
"422":
|
|
$ref: "#/components/responses/ValidationError"
|
|
"429":
|
|
$ref: "#/components/responses/TooManyRequests"
|
|
|
|
post:
|
|
operationId: createPage
|
|
tags: [Pages]
|
|
summary: Create a new page
|
|
description: |
|
|
Create a new page at the given route. The parent page must already
|
|
exist (except when creating top-level pages). Supply an optional
|
|
`order` to create an ordered (visible) page, or pass `"auto"` to let
|
|
the server pick the next free prefix based on existing siblings.
|
|
Include `lang` in the body to create the page in a specific language.
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/CreatePageRequest"
|
|
example:
|
|
route: /blog/my-new-post
|
|
title: My New Post
|
|
template: blog
|
|
content: |
|
|
# Hello World
|
|
|
|
This is the body of my new blog post.
|
|
header:
|
|
taxonomy:
|
|
category: [blog]
|
|
tag: [new, grav]
|
|
order: 3
|
|
responses:
|
|
"201":
|
|
description: Page created successfully.
|
|
headers:
|
|
Location:
|
|
description: URL of the newly created page resource.
|
|
schema:
|
|
type: string
|
|
example: /api/v1/pages/blog/my-new-post
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
properties:
|
|
data:
|
|
$ref: "#/components/schemas/Page"
|
|
"401":
|
|
$ref: "#/components/responses/Unauthorized"
|
|
"403":
|
|
$ref: "#/components/responses/Forbidden"
|
|
"422":
|
|
$ref: "#/components/responses/ValidationError"
|
|
"429":
|
|
$ref: "#/components/responses/TooManyRequests"
|
|
|
|
/pages/{route}:
|
|
get:
|
|
operationId: getPage
|
|
tags: [Pages]
|
|
summary: Get a single page
|
|
description: |
|
|
Retrieve a single page by its route. Returns the full page object
|
|
including raw Markdown content and media. Optionally render the
|
|
content to HTML and/or include child pages.
|
|
|
|
The response includes an `ETag` header for use with optimistic
|
|
concurrency on subsequent PATCH requests.
|
|
parameters:
|
|
- $ref: "#/components/parameters/pageRoute"
|
|
- $ref: "#/components/parameters/lang"
|
|
- name: render
|
|
in: query
|
|
description: When `true`, include the rendered HTML in `content_html`.
|
|
schema:
|
|
type: boolean
|
|
default: false
|
|
- name: children
|
|
in: query
|
|
description: When `true`, include child pages in the response.
|
|
schema:
|
|
type: boolean
|
|
default: false
|
|
- name: children_depth
|
|
in: query
|
|
description: Maximum depth for recursive child inclusion. Only effective when `children=true`.
|
|
schema:
|
|
type: integer
|
|
minimum: 1
|
|
default: 1
|
|
- name: translations
|
|
in: query
|
|
description: |
|
|
When `true`, include `translated_languages` and `untranslated_languages`
|
|
arrays in the response.
|
|
schema:
|
|
type: boolean
|
|
default: false
|
|
responses:
|
|
"200":
|
|
description: Page returned successfully.
|
|
headers:
|
|
ETag:
|
|
$ref: "#/components/headers/ETag"
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
properties:
|
|
data:
|
|
$ref: "#/components/schemas/Page"
|
|
"401":
|
|
$ref: "#/components/responses/Unauthorized"
|
|
"403":
|
|
$ref: "#/components/responses/Forbidden"
|
|
"404":
|
|
$ref: "#/components/responses/NotFound"
|
|
"429":
|
|
$ref: "#/components/responses/TooManyRequests"
|
|
|
|
patch:
|
|
operationId: updatePage
|
|
tags: [Pages]
|
|
summary: Partially update a page
|
|
description: |
|
|
Apply a partial update to an existing page. Only the fields included
|
|
in the request body are modified; all others are left unchanged.
|
|
|
|
**Optimistic concurrency:** If the `If-Match` header is provided, it
|
|
is compared against the current ETag. A `409 Conflict` is returned if
|
|
the resource has been modified since the client last fetched it.
|
|
parameters:
|
|
- $ref: "#/components/parameters/pageRoute"
|
|
- $ref: "#/components/parameters/lang"
|
|
- $ref: "#/components/parameters/ifMatch"
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/UpdatePageRequest"
|
|
example:
|
|
title: Updated Post Title
|
|
content: |
|
|
# Updated Content
|
|
|
|
The body has been revised.
|
|
header:
|
|
taxonomy:
|
|
tag: [updated]
|
|
published: true
|
|
responses:
|
|
"200":
|
|
description: Page updated successfully.
|
|
headers:
|
|
ETag:
|
|
$ref: "#/components/headers/ETag"
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
properties:
|
|
data:
|
|
$ref: "#/components/schemas/Page"
|
|
"401":
|
|
$ref: "#/components/responses/Unauthorized"
|
|
"403":
|
|
$ref: "#/components/responses/Forbidden"
|
|
"404":
|
|
$ref: "#/components/responses/NotFound"
|
|
"409":
|
|
$ref: "#/components/responses/Conflict"
|
|
"422":
|
|
$ref: "#/components/responses/ValidationError"
|
|
"429":
|
|
$ref: "#/components/responses/TooManyRequests"
|
|
|
|
delete:
|
|
operationId: deletePage
|
|
tags: [Pages]
|
|
summary: Delete a page
|
|
description: |
|
|
Delete a page and its directory from disk. By default, child pages
|
|
are deleted as well. Set `children=false` to prevent deletion of a
|
|
page that has children (a 422 error will be returned instead).
|
|
parameters:
|
|
- $ref: "#/components/parameters/pageRoute"
|
|
- $ref: "#/components/parameters/lang"
|
|
- name: children
|
|
in: query
|
|
description: |
|
|
Whether to also delete child pages. Set to `false` to prevent
|
|
accidental deletion of pages with children.
|
|
schema:
|
|
type: boolean
|
|
default: true
|
|
responses:
|
|
"204":
|
|
description: Page deleted successfully. No content returned.
|
|
"401":
|
|
$ref: "#/components/responses/Unauthorized"
|
|
"403":
|
|
$ref: "#/components/responses/Forbidden"
|
|
"404":
|
|
$ref: "#/components/responses/NotFound"
|
|
"422":
|
|
$ref: "#/components/responses/ValidationError"
|
|
"429":
|
|
$ref: "#/components/responses/TooManyRequests"
|
|
|
|
/pages/{route}/move:
|
|
post:
|
|
operationId: movePage
|
|
tags: [Pages]
|
|
summary: Move a page to a new location
|
|
description: |
|
|
Move an existing page to a different parent and/or change its slug or
|
|
ordering prefix. The destination parent must exist and the target
|
|
path must not already be occupied.
|
|
parameters:
|
|
- $ref: "#/components/parameters/pageRoute"
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/MovePageRequest"
|
|
example:
|
|
parent: /tutorials
|
|
slug: renamed-post
|
|
order: 5
|
|
responses:
|
|
"200":
|
|
description: Page moved successfully.
|
|
headers:
|
|
ETag:
|
|
$ref: "#/components/headers/ETag"
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
properties:
|
|
data:
|
|
$ref: "#/components/schemas/Page"
|
|
"401":
|
|
$ref: "#/components/responses/Unauthorized"
|
|
"403":
|
|
$ref: "#/components/responses/Forbidden"
|
|
"404":
|
|
$ref: "#/components/responses/NotFound"
|
|
"422":
|
|
$ref: "#/components/responses/ValidationError"
|
|
"429":
|
|
$ref: "#/components/responses/TooManyRequests"
|
|
|
|
/pages/{route}/copy:
|
|
post:
|
|
operationId: copyPage
|
|
tags: [Pages]
|
|
summary: Copy a page to a new location
|
|
description: |
|
|
Create a duplicate of an existing page (including all of its files)
|
|
at a new route. The destination parent must exist and the target
|
|
route must not already be occupied.
|
|
parameters:
|
|
- $ref: "#/components/parameters/pageRoute"
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
required: [route]
|
|
properties:
|
|
route:
|
|
type: string
|
|
description: The full route for the copied page.
|
|
example: /blog/my-post-copy
|
|
example:
|
|
route: /blog/my-post-copy
|
|
responses:
|
|
"201":
|
|
description: Page copied successfully.
|
|
headers:
|
|
Location:
|
|
description: URL of the newly created page resource.
|
|
schema:
|
|
type: string
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
properties:
|
|
data:
|
|
$ref: "#/components/schemas/Page"
|
|
"401":
|
|
$ref: "#/components/responses/Unauthorized"
|
|
"403":
|
|
$ref: "#/components/responses/Forbidden"
|
|
"404":
|
|
$ref: "#/components/responses/NotFound"
|
|
"422":
|
|
$ref: "#/components/responses/ValidationError"
|
|
"429":
|
|
$ref: "#/components/responses/TooManyRequests"
|
|
|
|
/pages/{route}/languages:
|
|
get:
|
|
operationId: listPageTranslations
|
|
tags: [Pages]
|
|
summary: List translations for a page
|
|
description: |
|
|
Return the list of languages the page has been translated into and
|
|
those still missing. Useful for building translation management UIs.
|
|
parameters:
|
|
- $ref: "#/components/parameters/pageRoute"
|
|
responses:
|
|
"200":
|
|
description: Translation status for the page.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
properties:
|
|
data:
|
|
$ref: "#/components/schemas/TranslationInfo"
|
|
example:
|
|
data:
|
|
route: /blog/my-first-post
|
|
default_language: en
|
|
translated:
|
|
en:
|
|
title: My First Post
|
|
last_modified: "2026-03-22T09:30:00+00:00"
|
|
fr:
|
|
title: Mon Premier Article
|
|
last_modified: "2026-03-23T11:00:00+00:00"
|
|
untranslated:
|
|
- de
|
|
- es
|
|
"401":
|
|
$ref: "#/components/responses/Unauthorized"
|
|
"403":
|
|
$ref: "#/components/responses/Forbidden"
|
|
"404":
|
|
$ref: "#/components/responses/NotFound"
|
|
"429":
|
|
$ref: "#/components/responses/TooManyRequests"
|
|
|
|
/pages/{route}/translate:
|
|
post:
|
|
operationId: createPageTranslation
|
|
tags: [Pages]
|
|
summary: Create a translation for a page
|
|
description: |
|
|
Create a new language translation for an existing page. The `lang`
|
|
field specifies the target language code. If a translation already
|
|
exists for that language, a `409 Conflict` is returned.
|
|
parameters:
|
|
- $ref: "#/components/parameters/pageRoute"
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
required: [lang]
|
|
properties:
|
|
lang:
|
|
type: string
|
|
description: Two-letter language code for the translation.
|
|
example: fr
|
|
title:
|
|
type: string
|
|
description: Page title in the target language.
|
|
example: Mon Premier Article
|
|
content:
|
|
type: string
|
|
description: Raw Markdown body in the target language.
|
|
example: |
|
|
# Bonjour le Monde
|
|
|
|
Ceci est le corps de l'article.
|
|
header:
|
|
type: object
|
|
additionalProperties: true
|
|
description: Additional frontmatter fields for the translation.
|
|
example:
|
|
lang: fr
|
|
title: Mon Premier Article
|
|
content: |
|
|
# Bonjour le Monde
|
|
|
|
Ceci est le corps de l'article.
|
|
responses:
|
|
"201":
|
|
description: Translation created successfully.
|
|
headers:
|
|
Location:
|
|
description: URL of the page in the new language.
|
|
schema:
|
|
type: string
|
|
example: /api/v1/pages/blog/my-first-post?lang=fr
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
properties:
|
|
data:
|
|
$ref: "#/components/schemas/Page"
|
|
"401":
|
|
$ref: "#/components/responses/Unauthorized"
|
|
"403":
|
|
$ref: "#/components/responses/Forbidden"
|
|
"404":
|
|
$ref: "#/components/responses/NotFound"
|
|
"409":
|
|
$ref: "#/components/responses/Conflict"
|
|
"422":
|
|
$ref: "#/components/responses/ValidationError"
|
|
"429":
|
|
$ref: "#/components/responses/TooManyRequests"
|
|
|
|
/pages/batch:
|
|
post:
|
|
operationId: batchPages
|
|
tags: [Pages]
|
|
summary: Batch operations on multiple pages
|
|
description: |
|
|
Apply the same operation to multiple pages at once. Supported
|
|
operations are `publish`, `unpublish`, `delete`, and `copy`. Each
|
|
route is processed independently; partial failures are reported in
|
|
the results array.
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
required: [operation, routes]
|
|
properties:
|
|
operation:
|
|
type: string
|
|
enum: [publish, unpublish, delete, copy]
|
|
description: The operation to apply to all specified pages.
|
|
example: publish
|
|
routes:
|
|
type: array
|
|
items:
|
|
type: string
|
|
description: Array of page routes to operate on.
|
|
example: [/blog/draft-one, /blog/draft-two]
|
|
options:
|
|
type: object
|
|
additionalProperties: true
|
|
description: |
|
|
Optional parameters for the operation. For `copy`, you may
|
|
provide `{parent: "/target"}` to copy all pages under a
|
|
new parent.
|
|
example:
|
|
operation: publish
|
|
routes:
|
|
- /blog/draft-one
|
|
- /blog/draft-two
|
|
responses:
|
|
"200":
|
|
description: Batch operation completed. Check individual results for status.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
properties:
|
|
data:
|
|
type: array
|
|
items:
|
|
$ref: "#/components/schemas/BatchResult"
|
|
example:
|
|
data:
|
|
- route: /blog/draft-one
|
|
status: success
|
|
- route: /blog/draft-two
|
|
status: success
|
|
"401":
|
|
$ref: "#/components/responses/Unauthorized"
|
|
"403":
|
|
$ref: "#/components/responses/Forbidden"
|
|
"422":
|
|
$ref: "#/components/responses/ValidationError"
|
|
"429":
|
|
$ref: "#/components/responses/TooManyRequests"
|
|
|
|
/pages/reorganize:
|
|
post:
|
|
operationId: reorganizePages
|
|
tags: [Pages]
|
|
summary: Reorganize multiple pages (move and reorder atomically)
|
|
description: |
|
|
Move and/or reorder multiple pages in a single atomic operation. Each
|
|
operation specifies a page route and optionally a new parent and/or
|
|
position. All operations are validated before any changes are applied.
|
|
|
|
Use this endpoint for drag-and-drop tree reorganization where pages may
|
|
be moved across parents and reordered within the same request.
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
required: [operations]
|
|
properties:
|
|
operations:
|
|
type: array
|
|
items:
|
|
type: object
|
|
required: [route]
|
|
properties:
|
|
route:
|
|
type: string
|
|
description: Current route of the page to reorganize.
|
|
example: /blog/post-a
|
|
parent:
|
|
type: string
|
|
description: |
|
|
New parent route. Use `/` for root. Omit to keep the
|
|
page under its current parent.
|
|
example: /tutorials
|
|
position:
|
|
type: integer
|
|
minimum: 1
|
|
description: |
|
|
1-based ordering position under the target parent. Omit
|
|
to keep the current order value.
|
|
example: 1
|
|
description: |
|
|
Array of reorganize operations to apply atomically. All are
|
|
validated before any filesystem changes are made.
|
|
example:
|
|
operations:
|
|
- route: /blog/post-a
|
|
parent: /tutorials
|
|
position: 1
|
|
- route: /blog/post-b
|
|
position: 2
|
|
- route: /tutorials/old-guide
|
|
parent: /blog
|
|
position: 3
|
|
responses:
|
|
"200":
|
|
description: Pages reorganized successfully.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
properties:
|
|
data:
|
|
type: array
|
|
items:
|
|
type: object
|
|
properties:
|
|
route:
|
|
type: string
|
|
example: /tutorials/post-a
|
|
slug:
|
|
type: string
|
|
example: post-a
|
|
title:
|
|
type: string
|
|
example: Post A
|
|
order:
|
|
type: integer
|
|
nullable: true
|
|
example: 1
|
|
parent:
|
|
type: string
|
|
example: /tutorials
|
|
example:
|
|
data:
|
|
- route: /tutorials/post-a
|
|
slug: post-a
|
|
title: Post A
|
|
order: 1
|
|
parent: /tutorials
|
|
- route: /blog/post-b
|
|
slug: post-b
|
|
title: Post B
|
|
order: 2
|
|
parent: /blog
|
|
"401":
|
|
$ref: "#/components/responses/Unauthorized"
|
|
"403":
|
|
$ref: "#/components/responses/Forbidden"
|
|
"422":
|
|
$ref: "#/components/responses/ValidationError"
|
|
"429":
|
|
$ref: "#/components/responses/TooManyRequests"
|
|
|
|
/pages/{route}/reorder:
|
|
post:
|
|
operationId: reorderPages
|
|
tags: [Pages]
|
|
summary: Reorder children of a page
|
|
description: |
|
|
Set the ordering of child pages under the specified parent route.
|
|
The `order` array must contain the slugs of all immediate children
|
|
in the desired order. Children not listed retain their current
|
|
position.
|
|
parameters:
|
|
- $ref: "#/components/parameters/pageRoute"
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
required: [order]
|
|
properties:
|
|
order:
|
|
type: array
|
|
items:
|
|
type: string
|
|
description: Ordered list of child slugs.
|
|
example: [third-post, first-post, second-post]
|
|
example:
|
|
order:
|
|
- third-post
|
|
- first-post
|
|
- second-post
|
|
responses:
|
|
"200":
|
|
description: Children reordered successfully.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
properties:
|
|
data:
|
|
type: array
|
|
items:
|
|
$ref: "#/components/schemas/PageSummary"
|
|
example:
|
|
data:
|
|
- route: /blog/third-post
|
|
slug: third-post
|
|
title: Third Post
|
|
template: blog
|
|
header: {}
|
|
taxonomy: {}
|
|
published: true
|
|
visible: true
|
|
routable: true
|
|
date: "2026-03-20T12:00:00+00:00"
|
|
modified: "2026-03-22T09:30:00+00:00"
|
|
order: 1
|
|
language: en
|
|
"401":
|
|
$ref: "#/components/responses/Unauthorized"
|
|
"403":
|
|
$ref: "#/components/responses/Forbidden"
|
|
"404":
|
|
$ref: "#/components/responses/NotFound"
|
|
"422":
|
|
$ref: "#/components/responses/ValidationError"
|
|
"429":
|
|
$ref: "#/components/responses/TooManyRequests"
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Page Media
|
|
# ---------------------------------------------------------------------------
|
|
/pages/{route}/media:
|
|
get:
|
|
operationId: listPageMedia
|
|
tags: [Media]
|
|
summary: List media for a page
|
|
description: Return all media files attached to the specified page.
|
|
parameters:
|
|
- $ref: "#/components/parameters/pageRoute"
|
|
responses:
|
|
"200":
|
|
description: List of media items for the page.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
properties:
|
|
data:
|
|
type: array
|
|
items:
|
|
$ref: "#/components/schemas/MediaItem"
|
|
"401":
|
|
$ref: "#/components/responses/Unauthorized"
|
|
"403":
|
|
$ref: "#/components/responses/Forbidden"
|
|
"404":
|
|
$ref: "#/components/responses/NotFound"
|
|
"429":
|
|
$ref: "#/components/responses/TooManyRequests"
|
|
|
|
post:
|
|
operationId: uploadPageMedia
|
|
tags: [Media]
|
|
summary: Upload media to a page
|
|
description: |
|
|
Upload one or more files to the specified page's directory. Files are
|
|
sent as `multipart/form-data`. The maximum upload size per file is
|
|
64 MB. Dangerous file extensions (as configured in Grav's security
|
|
settings) are rejected.
|
|
parameters:
|
|
- $ref: "#/components/parameters/pageRoute"
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
multipart/form-data:
|
|
schema:
|
|
type: object
|
|
properties:
|
|
file:
|
|
type: string
|
|
format: binary
|
|
description: One or more files to upload.
|
|
files:
|
|
type: array
|
|
items:
|
|
type: string
|
|
format: binary
|
|
description: Alternative field name for uploading multiple files.
|
|
responses:
|
|
"201":
|
|
description: Files uploaded successfully.
|
|
headers:
|
|
Location:
|
|
description: URL of the page media collection.
|
|
schema:
|
|
type: string
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
properties:
|
|
data:
|
|
type: array
|
|
items:
|
|
$ref: "#/components/schemas/MediaItem"
|
|
"401":
|
|
$ref: "#/components/responses/Unauthorized"
|
|
"403":
|
|
$ref: "#/components/responses/Forbidden"
|
|
"404":
|
|
$ref: "#/components/responses/NotFound"
|
|
"422":
|
|
$ref: "#/components/responses/ValidationError"
|
|
"429":
|
|
$ref: "#/components/responses/TooManyRequests"
|
|
|
|
/pages/{route}/media/{filename}:
|
|
delete:
|
|
operationId: deletePageMedia
|
|
tags: [Media]
|
|
summary: Delete a media file from a page
|
|
description: |
|
|
Remove a single media file (and its `.meta.yaml` sidecar, if present)
|
|
from the specified page.
|
|
parameters:
|
|
- $ref: "#/components/parameters/pageRoute"
|
|
- name: filename
|
|
in: path
|
|
required: true
|
|
description: Name of the media file to delete.
|
|
schema:
|
|
type: string
|
|
example: hero-image.jpg
|
|
responses:
|
|
"204":
|
|
description: Media file deleted successfully. No content returned.
|
|
"401":
|
|
$ref: "#/components/responses/Unauthorized"
|
|
"403":
|
|
$ref: "#/components/responses/Forbidden"
|
|
"404":
|
|
$ref: "#/components/responses/NotFound"
|
|
"422":
|
|
$ref: "#/components/responses/ValidationError"
|
|
"429":
|
|
$ref: "#/components/responses/TooManyRequests"
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Site-level Media
|
|
# ---------------------------------------------------------------------------
|
|
/media:
|
|
get:
|
|
operationId: listSiteMedia
|
|
tags: [Media]
|
|
summary: List site-level media
|
|
description: |
|
|
Return a paginated list of files in the site-level media directory
|
|
(`user/media`).
|
|
parameters:
|
|
- $ref: "#/components/parameters/page"
|
|
- $ref: "#/components/parameters/per_page"
|
|
- name: path
|
|
in: query
|
|
description: Subfolder path relative to the media root directory.
|
|
schema:
|
|
type: string
|
|
example: blog/2026
|
|
- name: search
|
|
in: query
|
|
description: Search term for recursive filename matching across all subfolders.
|
|
schema:
|
|
type: string
|
|
example: hero
|
|
- name: type
|
|
in: query
|
|
description: Filter by media type category.
|
|
schema:
|
|
type: string
|
|
enum: [image, video, audio, document]
|
|
example: image
|
|
responses:
|
|
"200":
|
|
description: Paginated list of site media files.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
allOf:
|
|
- $ref: "#/components/schemas/PaginatedEnvelope"
|
|
- type: object
|
|
properties:
|
|
data:
|
|
type: array
|
|
items:
|
|
$ref: "#/components/schemas/MediaItem"
|
|
"401":
|
|
$ref: "#/components/responses/Unauthorized"
|
|
"403":
|
|
$ref: "#/components/responses/Forbidden"
|
|
"429":
|
|
$ref: "#/components/responses/TooManyRequests"
|
|
|
|
post:
|
|
operationId: uploadSiteMedia
|
|
tags: [Media]
|
|
summary: Upload site-level media
|
|
description: |
|
|
Upload one or more files to the site-level media directory
|
|
(`user/media`). The maximum upload size per file is 64 MB.
|
|
parameters:
|
|
- name: path
|
|
in: query
|
|
description: Subfolder path to upload into. Created automatically if it does not exist.
|
|
schema:
|
|
type: string
|
|
example: blog/2026
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
multipart/form-data:
|
|
schema:
|
|
type: object
|
|
properties:
|
|
file:
|
|
type: string
|
|
format: binary
|
|
description: One or more files to upload.
|
|
files:
|
|
type: array
|
|
items:
|
|
type: string
|
|
format: binary
|
|
description: Alternative field name for uploading multiple files.
|
|
responses:
|
|
"201":
|
|
description: Files uploaded successfully.
|
|
headers:
|
|
Location:
|
|
description: URL of the site media collection.
|
|
schema:
|
|
type: string
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
properties:
|
|
data:
|
|
type: array
|
|
items:
|
|
$ref: "#/components/schemas/MediaItem"
|
|
"401":
|
|
$ref: "#/components/responses/Unauthorized"
|
|
"403":
|
|
$ref: "#/components/responses/Forbidden"
|
|
"422":
|
|
$ref: "#/components/responses/ValidationError"
|
|
"429":
|
|
$ref: "#/components/responses/TooManyRequests"
|
|
|
|
/media/{filename}:
|
|
delete:
|
|
operationId: deleteSiteMedia
|
|
tags: [Media]
|
|
summary: Delete a site-level media file
|
|
description: |
|
|
Remove a file (and its `.meta.yaml` sidecar, if present) from the
|
|
site-level media directory.
|
|
parameters:
|
|
- name: filename
|
|
in: path
|
|
required: true
|
|
description: Name of the file to delete (may include subdirectory path).
|
|
schema:
|
|
type: string
|
|
example: hero-banner.jpg
|
|
responses:
|
|
"204":
|
|
description: File deleted successfully. No content returned.
|
|
"401":
|
|
$ref: "#/components/responses/Unauthorized"
|
|
"403":
|
|
$ref: "#/components/responses/Forbidden"
|
|
"404":
|
|
$ref: "#/components/responses/NotFound"
|
|
"429":
|
|
$ref: "#/components/responses/TooManyRequests"
|
|
|
|
/media/folders:
|
|
post:
|
|
operationId: createMediaFolder
|
|
tags: [Media]
|
|
summary: Create a media folder
|
|
description: |
|
|
Create a new folder in the site-level media directory (`user/media`).
|
|
Parent directories are created automatically if they do not exist.
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
required: [path]
|
|
properties:
|
|
path:
|
|
type: string
|
|
description: Folder path to create, relative to the media root.
|
|
example: blog/2026/april
|
|
responses:
|
|
"201":
|
|
description: Folder created successfully.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
properties:
|
|
data:
|
|
$ref: "#/components/schemas/FolderInfo"
|
|
"401":
|
|
$ref: "#/components/responses/Unauthorized"
|
|
"403":
|
|
$ref: "#/components/responses/Forbidden"
|
|
"422":
|
|
$ref: "#/components/responses/ValidationError"
|
|
"429":
|
|
$ref: "#/components/responses/TooManyRequests"
|
|
|
|
/media/folders/{path}:
|
|
delete:
|
|
operationId: deleteMediaFolder
|
|
tags: [Media]
|
|
summary: Delete an empty media folder
|
|
description: |
|
|
Remove an empty folder from the site-level media directory. The folder
|
|
must be empty (contain no files or subdirectories).
|
|
parameters:
|
|
- name: path
|
|
in: path
|
|
required: true
|
|
description: Folder path relative to the media root.
|
|
schema:
|
|
type: string
|
|
example: blog/2026
|
|
responses:
|
|
"204":
|
|
description: Folder deleted successfully. No content returned.
|
|
"401":
|
|
$ref: "#/components/responses/Unauthorized"
|
|
"403":
|
|
$ref: "#/components/responses/Forbidden"
|
|
"404":
|
|
$ref: "#/components/responses/NotFound"
|
|
"422":
|
|
$ref: "#/components/responses/ValidationError"
|
|
"429":
|
|
$ref: "#/components/responses/TooManyRequests"
|
|
|
|
/media/rename:
|
|
post:
|
|
operationId: renameSiteMedia
|
|
tags: [Media]
|
|
summary: Rename or move a media file
|
|
description: |
|
|
Rename a file or move it to a different folder within the site-level
|
|
media directory. The destination directory is created automatically
|
|
if it does not exist. Any `.meta.yaml` sidecar is also moved.
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
required: [from, to]
|
|
properties:
|
|
from:
|
|
type: string
|
|
description: Current file path relative to the media root.
|
|
example: blog/hero.jpg
|
|
to:
|
|
type: string
|
|
description: New file path relative to the media root.
|
|
example: blog/banner.jpg
|
|
responses:
|
|
"200":
|
|
description: File renamed successfully.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
properties:
|
|
data:
|
|
$ref: "#/components/schemas/MediaItem"
|
|
"401":
|
|
$ref: "#/components/responses/Unauthorized"
|
|
"403":
|
|
$ref: "#/components/responses/Forbidden"
|
|
"404":
|
|
$ref: "#/components/responses/NotFound"
|
|
"422":
|
|
$ref: "#/components/responses/ValidationError"
|
|
"429":
|
|
$ref: "#/components/responses/TooManyRequests"
|
|
|
|
/media/folders/rename:
|
|
post:
|
|
operationId: renameMediaFolder
|
|
tags: [Media]
|
|
summary: Rename a media folder
|
|
description: |
|
|
Rename a folder within the site-level media directory. All contents
|
|
of the folder are preserved.
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
required: [from, to]
|
|
properties:
|
|
from:
|
|
type: string
|
|
description: Current folder path relative to the media root.
|
|
example: blog/old-name
|
|
to:
|
|
type: string
|
|
description: New folder path relative to the media root.
|
|
example: blog/new-name
|
|
responses:
|
|
"200":
|
|
description: Folder renamed successfully.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
properties:
|
|
data:
|
|
$ref: "#/components/schemas/FolderInfo"
|
|
"401":
|
|
$ref: "#/components/responses/Unauthorized"
|
|
"403":
|
|
$ref: "#/components/responses/Forbidden"
|
|
"404":
|
|
$ref: "#/components/responses/NotFound"
|
|
"422":
|
|
$ref: "#/components/responses/ValidationError"
|
|
"429":
|
|
$ref: "#/components/responses/TooManyRequests"
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Taxonomy
|
|
# ---------------------------------------------------------------------------
|
|
/taxonomy:
|
|
get:
|
|
operationId: listTaxonomy
|
|
tags: [Pages]
|
|
summary: List all taxonomy types and values
|
|
description: |
|
|
Return every taxonomy type (e.g. `category`, `tag`) and the set of
|
|
values currently in use across all pages.
|
|
responses:
|
|
"200":
|
|
description: Taxonomy map returned successfully.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
properties:
|
|
data:
|
|
type: object
|
|
additionalProperties:
|
|
type: object
|
|
description: |
|
|
Keys are taxonomy values; each value is an array of
|
|
page routes that use that taxonomy value.
|
|
additionalProperties:
|
|
type: array
|
|
items:
|
|
type: string
|
|
example:
|
|
category:
|
|
blog:
|
|
- /blog/post-one
|
|
- /blog/post-two
|
|
tutorials:
|
|
- /tutorials/getting-started
|
|
tag:
|
|
grav:
|
|
- /blog/post-one
|
|
php:
|
|
- /tutorials/getting-started
|
|
"401":
|
|
$ref: "#/components/responses/Unauthorized"
|
|
"403":
|
|
$ref: "#/components/responses/Forbidden"
|
|
"429":
|
|
$ref: "#/components/responses/TooManyRequests"
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Config
|
|
# ---------------------------------------------------------------------------
|
|
/config/{scope}:
|
|
get:
|
|
operationId: getConfig
|
|
tags: [Config]
|
|
summary: Read configuration for a scope
|
|
description: |
|
|
Retrieve the current configuration for the given scope. Supported
|
|
scopes are:
|
|
|
|
| Scope | Maps to |
|
|
|---|---|
|
|
| `system` | `system` config |
|
|
| `site` | `site` config |
|
|
| `plugins/{name}` | `plugins.{name}` config |
|
|
| `themes/{name}` | `themes.{name}` config |
|
|
|
|
The response includes an `ETag` header for optimistic concurrency on
|
|
subsequent PATCH requests.
|
|
parameters:
|
|
- $ref: "#/components/parameters/configScope"
|
|
responses:
|
|
"200":
|
|
description: Configuration returned successfully.
|
|
headers:
|
|
ETag:
|
|
$ref: "#/components/headers/ETag"
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
properties:
|
|
data:
|
|
$ref: "#/components/schemas/ConfigObject"
|
|
"401":
|
|
$ref: "#/components/responses/Unauthorized"
|
|
"403":
|
|
$ref: "#/components/responses/Forbidden"
|
|
"404":
|
|
$ref: "#/components/responses/NotFound"
|
|
"429":
|
|
$ref: "#/components/responses/TooManyRequests"
|
|
|
|
patch:
|
|
operationId: updateConfig
|
|
tags: [Config]
|
|
summary: Partially update configuration
|
|
description: |
|
|
Deep-merge the provided values into the existing configuration for the
|
|
given scope. The merged result is persisted to the corresponding YAML
|
|
file and the config cache is cleared.
|
|
|
|
Send the current `ETag` in `If-Match` to detect concurrent edits.
|
|
parameters:
|
|
- $ref: "#/components/parameters/configScope"
|
|
- $ref: "#/components/parameters/ifMatch"
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/ConfigObject"
|
|
example:
|
|
pages:
|
|
theme: quark
|
|
debugger:
|
|
enabled: false
|
|
responses:
|
|
"200":
|
|
description: Configuration updated successfully.
|
|
headers:
|
|
ETag:
|
|
$ref: "#/components/headers/ETag"
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
properties:
|
|
data:
|
|
$ref: "#/components/schemas/ConfigObject"
|
|
"401":
|
|
$ref: "#/components/responses/Unauthorized"
|
|
"403":
|
|
$ref: "#/components/responses/Forbidden"
|
|
"404":
|
|
$ref: "#/components/responses/NotFound"
|
|
"409":
|
|
$ref: "#/components/responses/Conflict"
|
|
"422":
|
|
$ref: "#/components/responses/ValidationError"
|
|
"429":
|
|
$ref: "#/components/responses/TooManyRequests"
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Users
|
|
# ---------------------------------------------------------------------------
|
|
/users:
|
|
get:
|
|
operationId: listUsers
|
|
tags: [Users]
|
|
summary: List all users
|
|
description: Retrieve a paginated list of all user accounts.
|
|
parameters:
|
|
- $ref: "#/components/parameters/page"
|
|
- $ref: "#/components/parameters/per_page"
|
|
responses:
|
|
"200":
|
|
description: Paginated list of users.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
allOf:
|
|
- $ref: "#/components/schemas/PaginatedEnvelope"
|
|
- type: object
|
|
properties:
|
|
data:
|
|
type: array
|
|
items:
|
|
$ref: "#/components/schemas/User"
|
|
"401":
|
|
$ref: "#/components/responses/Unauthorized"
|
|
"403":
|
|
$ref: "#/components/responses/Forbidden"
|
|
"429":
|
|
$ref: "#/components/responses/TooManyRequests"
|
|
|
|
post:
|
|
operationId: createUser
|
|
tags: [Users]
|
|
summary: Create a new user
|
|
description: |
|
|
Create a new user account. The username must be 3-64 characters and
|
|
contain only letters, numbers, hyphens, and underscores.
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/CreateUserRequest"
|
|
example:
|
|
username: johndoe
|
|
password: s3cureP@ss
|
|
email: john@example.com
|
|
fullname: John Doe
|
|
title: Editor
|
|
state: enabled
|
|
access:
|
|
api:
|
|
access: true
|
|
pages:
|
|
read: true
|
|
write: true
|
|
responses:
|
|
"201":
|
|
description: User created successfully.
|
|
headers:
|
|
Location:
|
|
description: URL of the newly created user resource.
|
|
schema:
|
|
type: string
|
|
example: /api/v1/users/johndoe
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
properties:
|
|
data:
|
|
$ref: "#/components/schemas/User"
|
|
"401":
|
|
$ref: "#/components/responses/Unauthorized"
|
|
"403":
|
|
$ref: "#/components/responses/Forbidden"
|
|
"409":
|
|
$ref: "#/components/responses/Conflict"
|
|
"422":
|
|
$ref: "#/components/responses/ValidationError"
|
|
"429":
|
|
$ref: "#/components/responses/TooManyRequests"
|
|
|
|
/users/{username}:
|
|
get:
|
|
operationId: getUser
|
|
tags: [Users]
|
|
summary: Get a single user
|
|
description: |
|
|
Retrieve a user account by username. The response includes an `ETag`
|
|
header for optimistic concurrency on PATCH requests.
|
|
parameters:
|
|
- $ref: "#/components/parameters/username"
|
|
responses:
|
|
"200":
|
|
description: User returned successfully.
|
|
headers:
|
|
ETag:
|
|
$ref: "#/components/headers/ETag"
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
properties:
|
|
data:
|
|
$ref: "#/components/schemas/User"
|
|
"401":
|
|
$ref: "#/components/responses/Unauthorized"
|
|
"403":
|
|
$ref: "#/components/responses/Forbidden"
|
|
"404":
|
|
$ref: "#/components/responses/NotFound"
|
|
"429":
|
|
$ref: "#/components/responses/TooManyRequests"
|
|
|
|
patch:
|
|
operationId: updateUser
|
|
tags: [Users]
|
|
summary: Partially update a user
|
|
description: |
|
|
Apply a partial update to an existing user account. Users can update
|
|
their own account with the basic `api.access` permission; updating
|
|
other users requires `api.users.write`.
|
|
|
|
Send the current `ETag` in `If-Match` to detect concurrent edits.
|
|
parameters:
|
|
- $ref: "#/components/parameters/username"
|
|
- $ref: "#/components/parameters/ifMatch"
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/UpdateUserRequest"
|
|
example:
|
|
fullname: Jane Doe
|
|
email: jane@example.com
|
|
state: enabled
|
|
responses:
|
|
"200":
|
|
description: User updated successfully.
|
|
headers:
|
|
ETag:
|
|
$ref: "#/components/headers/ETag"
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
properties:
|
|
data:
|
|
$ref: "#/components/schemas/User"
|
|
"401":
|
|
$ref: "#/components/responses/Unauthorized"
|
|
"403":
|
|
$ref: "#/components/responses/Forbidden"
|
|
"404":
|
|
$ref: "#/components/responses/NotFound"
|
|
"409":
|
|
$ref: "#/components/responses/Conflict"
|
|
"422":
|
|
$ref: "#/components/responses/ValidationError"
|
|
"429":
|
|
$ref: "#/components/responses/TooManyRequests"
|
|
|
|
delete:
|
|
operationId: deleteUser
|
|
tags: [Users]
|
|
summary: Delete a user
|
|
description: |
|
|
Permanently remove a user account. You cannot delete your own account;
|
|
a `403 Forbidden` is returned if you try.
|
|
parameters:
|
|
- $ref: "#/components/parameters/username"
|
|
responses:
|
|
"204":
|
|
description: User deleted successfully. No content returned.
|
|
"401":
|
|
$ref: "#/components/responses/Unauthorized"
|
|
"403":
|
|
$ref: "#/components/responses/Forbidden"
|
|
"404":
|
|
$ref: "#/components/responses/NotFound"
|
|
"429":
|
|
$ref: "#/components/responses/TooManyRequests"
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# User Avatar
|
|
# ---------------------------------------------------------------------------
|
|
/users/{username}/avatar:
|
|
post:
|
|
operationId: uploadUserAvatar
|
|
tags: [Users]
|
|
summary: Upload user avatar
|
|
description: |
|
|
Upload or replace the avatar image for the specified user. The image is
|
|
sent as `multipart/form-data` with a single `avatar` field. Users can
|
|
update their own avatar with `api.access`; updating another user's
|
|
avatar requires `api.users.write`.
|
|
parameters:
|
|
- $ref: "#/components/parameters/username"
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
multipart/form-data:
|
|
schema:
|
|
type: object
|
|
required: [avatar]
|
|
properties:
|
|
avatar:
|
|
type: string
|
|
format: binary
|
|
description: Avatar image file to upload.
|
|
responses:
|
|
"200":
|
|
description: Avatar uploaded successfully. Returns the full user object.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
properties:
|
|
data:
|
|
$ref: "#/components/schemas/User"
|
|
"401":
|
|
$ref: "#/components/responses/Unauthorized"
|
|
"403":
|
|
$ref: "#/components/responses/Forbidden"
|
|
"404":
|
|
$ref: "#/components/responses/NotFound"
|
|
"422":
|
|
$ref: "#/components/responses/ValidationError"
|
|
"429":
|
|
$ref: "#/components/responses/TooManyRequests"
|
|
|
|
delete:
|
|
operationId: deleteUserAvatar
|
|
tags: [Users]
|
|
summary: Remove user avatar
|
|
description: |
|
|
Remove the avatar image for the specified user. Users can remove their
|
|
own avatar with `api.access`; removing another user's avatar requires
|
|
`api.users.write`.
|
|
parameters:
|
|
- $ref: "#/components/parameters/username"
|
|
responses:
|
|
"200":
|
|
description: Avatar removed successfully. Returns the full user object.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
properties:
|
|
data:
|
|
$ref: "#/components/schemas/User"
|
|
"401":
|
|
$ref: "#/components/responses/Unauthorized"
|
|
"403":
|
|
$ref: "#/components/responses/Forbidden"
|
|
"404":
|
|
$ref: "#/components/responses/NotFound"
|
|
"429":
|
|
$ref: "#/components/responses/TooManyRequests"
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# User 2FA
|
|
# ---------------------------------------------------------------------------
|
|
/users/{username}/2fa:
|
|
post:
|
|
operationId: generateUser2FA
|
|
tags: [Users]
|
|
summary: Generate 2FA secret and QR code
|
|
description: |
|
|
Generate a new two-factor authentication secret and QR code data URI
|
|
for the specified user. The client should display the QR code for the
|
|
user to scan with their authenticator app, then confirm activation
|
|
via a PATCH to the user resource.
|
|
parameters:
|
|
- $ref: "#/components/parameters/username"
|
|
responses:
|
|
"200":
|
|
description: 2FA secret and QR code generated.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
properties:
|
|
data:
|
|
type: object
|
|
properties:
|
|
secret:
|
|
type: string
|
|
description: The TOTP secret key.
|
|
example: JBSWY3DPEHPK3PXP
|
|
qr_code:
|
|
type: string
|
|
description: QR code as a data URI (PNG image encoded as base64).
|
|
example: "data:image/png;base64,iVBORw0KGgo..."
|
|
example:
|
|
data:
|
|
secret: JBSWY3DPEHPK3PXP
|
|
qr_code: "data:image/png;base64,iVBORw0KGgo..."
|
|
"401":
|
|
$ref: "#/components/responses/Unauthorized"
|
|
"403":
|
|
$ref: "#/components/responses/Forbidden"
|
|
"404":
|
|
$ref: "#/components/responses/NotFound"
|
|
"429":
|
|
$ref: "#/components/responses/TooManyRequests"
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# API Keys
|
|
# ---------------------------------------------------------------------------
|
|
/users/{username}/api-keys:
|
|
get:
|
|
operationId: listApiKeys
|
|
tags: [Users]
|
|
summary: List API keys for a user
|
|
description: |
|
|
Return all API keys for the specified user. The response includes
|
|
key metadata but never the key hash. Users can list their own keys
|
|
with `api.access`; listing another user's keys requires
|
|
`api.users.read`.
|
|
parameters:
|
|
- $ref: "#/components/parameters/username"
|
|
responses:
|
|
"200":
|
|
description: List of API keys.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
properties:
|
|
data:
|
|
type: array
|
|
items:
|
|
$ref: "#/components/schemas/ApiKey"
|
|
"401":
|
|
$ref: "#/components/responses/Unauthorized"
|
|
"403":
|
|
$ref: "#/components/responses/Forbidden"
|
|
"404":
|
|
$ref: "#/components/responses/NotFound"
|
|
"429":
|
|
$ref: "#/components/responses/TooManyRequests"
|
|
|
|
post:
|
|
operationId: createApiKey
|
|
tags: [Users]
|
|
summary: Create a new API key
|
|
description: |
|
|
Generate a new API key for the specified user. The raw key value is
|
|
returned **only once** in the `api_key` field of the response -- it
|
|
cannot be retrieved again.
|
|
|
|
Users can create keys for their own account with `api.access`;
|
|
creating keys for other users requires `api.users.write`.
|
|
parameters:
|
|
- $ref: "#/components/parameters/username"
|
|
requestBody:
|
|
required: false
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
properties:
|
|
name:
|
|
type: string
|
|
description: A human-readable label for the key.
|
|
default: API Key
|
|
example: CI/CD Pipeline
|
|
scopes:
|
|
type: array
|
|
items:
|
|
type: string
|
|
description: Optional list of permission scopes to restrict this key.
|
|
example: [api.pages.read, api.media.read]
|
|
expiry_days:
|
|
type: integer
|
|
nullable: true
|
|
description: Number of days until the key expires. Omit or set to null for no expiry.
|
|
example: 90
|
|
example:
|
|
name: CI/CD Pipeline
|
|
scopes: [api.pages.read, api.media.read]
|
|
expiry_days: 90
|
|
responses:
|
|
"201":
|
|
description: API key created. The raw key is returned only in this response.
|
|
headers:
|
|
Location:
|
|
description: URL of the user's API keys collection.
|
|
schema:
|
|
type: string
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
properties:
|
|
data:
|
|
allOf:
|
|
- $ref: "#/components/schemas/ApiKey"
|
|
- type: object
|
|
properties:
|
|
api_key:
|
|
type: string
|
|
description: |
|
|
The raw API key. Store it securely -- it is
|
|
shown only once and cannot be retrieved later.
|
|
example: grav_a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6
|
|
"401":
|
|
$ref: "#/components/responses/Unauthorized"
|
|
"403":
|
|
$ref: "#/components/responses/Forbidden"
|
|
"404":
|
|
$ref: "#/components/responses/NotFound"
|
|
"429":
|
|
$ref: "#/components/responses/TooManyRequests"
|
|
|
|
/users/{username}/api-keys/{keyId}:
|
|
delete:
|
|
operationId: deleteApiKey
|
|
tags: [Users]
|
|
summary: Revoke an API key
|
|
description: |
|
|
Permanently delete an API key. Any future requests using this key
|
|
will be rejected.
|
|
parameters:
|
|
- $ref: "#/components/parameters/username"
|
|
- name: keyId
|
|
in: path
|
|
required: true
|
|
description: Unique identifier of the API key.
|
|
schema:
|
|
type: string
|
|
example: a1b2c3d4e5f6a7b8
|
|
responses:
|
|
"204":
|
|
description: API key revoked. No content returned.
|
|
"401":
|
|
$ref: "#/components/responses/Unauthorized"
|
|
"403":
|
|
$ref: "#/components/responses/Forbidden"
|
|
"404":
|
|
$ref: "#/components/responses/NotFound"
|
|
"429":
|
|
$ref: "#/components/responses/TooManyRequests"
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# System
|
|
# ---------------------------------------------------------------------------
|
|
/system/environments:
|
|
get:
|
|
operationId: getEnvironments
|
|
summary: List available environments
|
|
tags: [System]
|
|
description: |
|
|
Returns the current Grav environment and all environment-specific
|
|
overrides found in `user/env/`.
|
|
responses:
|
|
'200':
|
|
description: Environment list
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
properties:
|
|
data:
|
|
type: object
|
|
properties:
|
|
current:
|
|
type: string
|
|
example: localhost
|
|
environments:
|
|
type: array
|
|
items:
|
|
type: object
|
|
properties:
|
|
name:
|
|
type: string
|
|
example: mysite.com
|
|
active:
|
|
type: boolean
|
|
'401':
|
|
$ref: '#/components/responses/Unauthorized'
|
|
|
|
/system/info:
|
|
get:
|
|
operationId: getSystemInfo
|
|
tags: [System]
|
|
summary: Get system information
|
|
description: |
|
|
Return runtime information about the Grav installation including
|
|
Grav version, PHP version, loaded extensions, installed plugins and
|
|
themes.
|
|
responses:
|
|
"200":
|
|
description: System information returned successfully.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
properties:
|
|
data:
|
|
$ref: "#/components/schemas/SystemInfo"
|
|
"401":
|
|
$ref: "#/components/responses/Unauthorized"
|
|
"403":
|
|
$ref: "#/components/responses/Forbidden"
|
|
"429":
|
|
$ref: "#/components/responses/TooManyRequests"
|
|
|
|
/cache:
|
|
delete:
|
|
operationId: clearCache
|
|
tags: [System]
|
|
summary: Clear the cache
|
|
description: |
|
|
Clear the Grav cache. The `scope` query parameter controls which
|
|
cache pool is purged.
|
|
parameters:
|
|
- name: scope
|
|
in: query
|
|
description: Cache scope to clear.
|
|
schema:
|
|
type: string
|
|
enum: [all, standard, images, assets, tmp]
|
|
default: standard
|
|
responses:
|
|
"200":
|
|
description: Cache cleared successfully.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
properties:
|
|
data:
|
|
type: object
|
|
properties:
|
|
scope:
|
|
type: string
|
|
example: standard
|
|
message:
|
|
type: string
|
|
example: "Cache cleared successfully (scope: standard)."
|
|
details:
|
|
type: array
|
|
items:
|
|
type: string
|
|
description: Per-directory results from the cache clear operation.
|
|
"401":
|
|
$ref: "#/components/responses/Unauthorized"
|
|
"403":
|
|
$ref: "#/components/responses/Forbidden"
|
|
"422":
|
|
$ref: "#/components/responses/ValidationError"
|
|
"429":
|
|
$ref: "#/components/responses/TooManyRequests"
|
|
|
|
/system/logs:
|
|
get:
|
|
operationId: getSystemLogs
|
|
tags: [System]
|
|
summary: Read log entries
|
|
description: |
|
|
Return paginated entries from the Grav log file (`grav.log`).
|
|
Entries are returned newest-first. Optionally filter by log level.
|
|
parameters:
|
|
- $ref: "#/components/parameters/page"
|
|
- $ref: "#/components/parameters/per_page"
|
|
- name: level
|
|
in: query
|
|
description: Filter entries by log level (case-insensitive).
|
|
schema:
|
|
type: string
|
|
enum: [DEBUG, INFO, NOTICE, WARNING, ERROR, CRITICAL, ALERT, EMERGENCY]
|
|
example: ERROR
|
|
responses:
|
|
"200":
|
|
description: Paginated list of log entries.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
allOf:
|
|
- $ref: "#/components/schemas/PaginatedEnvelope"
|
|
- type: object
|
|
properties:
|
|
data:
|
|
type: array
|
|
items:
|
|
$ref: "#/components/schemas/LogEntry"
|
|
"401":
|
|
$ref: "#/components/responses/Unauthorized"
|
|
"403":
|
|
$ref: "#/components/responses/Forbidden"
|
|
"429":
|
|
$ref: "#/components/responses/TooManyRequests"
|
|
|
|
/system/backup:
|
|
post:
|
|
operationId: createBackup
|
|
tags: [System]
|
|
summary: Create a backup
|
|
description: Trigger a full site backup and return metadata about the generated archive.
|
|
responses:
|
|
"201":
|
|
description: Backup created successfully.
|
|
headers:
|
|
Location:
|
|
description: URL of the backups list.
|
|
schema:
|
|
type: string
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
properties:
|
|
data:
|
|
$ref: "#/components/schemas/Backup"
|
|
"401":
|
|
$ref: "#/components/responses/Unauthorized"
|
|
"403":
|
|
$ref: "#/components/responses/Forbidden"
|
|
"429":
|
|
$ref: "#/components/responses/TooManyRequests"
|
|
|
|
/system/backups:
|
|
get:
|
|
operationId: listBackups
|
|
tags: [System]
|
|
summary: List existing backups
|
|
description: Return metadata for all backup archives found on disk.
|
|
responses:
|
|
"200":
|
|
description: List of backups.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
properties:
|
|
data:
|
|
type: array
|
|
items:
|
|
$ref: "#/components/schemas/Backup"
|
|
"401":
|
|
$ref: "#/components/responses/Unauthorized"
|
|
"403":
|
|
$ref: "#/components/responses/Forbidden"
|
|
"429":
|
|
$ref: "#/components/responses/TooManyRequests"
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Languages
|
|
# ---------------------------------------------------------------------------
|
|
/languages:
|
|
get:
|
|
operationId: listLanguages
|
|
tags: [Languages]
|
|
summary: List configured site languages
|
|
description: |
|
|
Return the language configuration for the site, including which
|
|
languages are enabled, the default language, and which language is
|
|
currently active based on the request context.
|
|
responses:
|
|
"200":
|
|
description: Language configuration returned successfully.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
properties:
|
|
data:
|
|
$ref: "#/components/schemas/LanguageInfo"
|
|
example:
|
|
data:
|
|
enabled: true
|
|
languages:
|
|
- en
|
|
- fr
|
|
- de
|
|
default: en
|
|
active: en
|
|
"401":
|
|
$ref: "#/components/responses/Unauthorized"
|
|
"403":
|
|
$ref: "#/components/responses/Forbidden"
|
|
"429":
|
|
$ref: "#/components/responses/TooManyRequests"
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# GPM
|
|
# ---------------------------------------------------------------------------
|
|
/gpm/plugins:
|
|
get:
|
|
operationId: listInstalledPlugins
|
|
tags: [GPM]
|
|
summary: List installed plugins
|
|
description: |
|
|
Return a list of all installed plugins with their version, enabled
|
|
state, and whether an update is available.
|
|
responses:
|
|
"200":
|
|
description: List of installed plugins.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
properties:
|
|
data:
|
|
type: array
|
|
items:
|
|
$ref: "#/components/schemas/Package"
|
|
example:
|
|
data:
|
|
- slug: api
|
|
name: API
|
|
version: 1.0.0-beta.1
|
|
type: plugin
|
|
enabled: true
|
|
update_available: false
|
|
available_version: null
|
|
author: Team Grav
|
|
description: RESTful API for Grav CMS.
|
|
"401":
|
|
$ref: "#/components/responses/Unauthorized"
|
|
"403":
|
|
$ref: "#/components/responses/Forbidden"
|
|
"429":
|
|
$ref: "#/components/responses/TooManyRequests"
|
|
|
|
/gpm/plugins/{slug}:
|
|
get:
|
|
operationId: getInstalledPlugin
|
|
tags: [GPM]
|
|
summary: Get installed plugin details
|
|
description: |
|
|
Return detailed information about a specific installed plugin
|
|
including its configuration blueprint, README, and changelog.
|
|
parameters:
|
|
- name: slug
|
|
in: path
|
|
required: true
|
|
description: Plugin slug (e.g. `admin`, `api`).
|
|
schema:
|
|
type: string
|
|
example: api
|
|
responses:
|
|
"200":
|
|
description: Plugin details returned successfully.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
properties:
|
|
data:
|
|
$ref: "#/components/schemas/Package"
|
|
"401":
|
|
$ref: "#/components/responses/Unauthorized"
|
|
"403":
|
|
$ref: "#/components/responses/Forbidden"
|
|
"404":
|
|
$ref: "#/components/responses/NotFound"
|
|
"429":
|
|
$ref: "#/components/responses/TooManyRequests"
|
|
|
|
/gpm/themes:
|
|
get:
|
|
operationId: listInstalledThemes
|
|
tags: [GPM]
|
|
summary: List installed themes
|
|
description: |
|
|
Return a list of all installed themes with their version, active
|
|
state, and whether an update is available.
|
|
responses:
|
|
"200":
|
|
description: List of installed themes.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
properties:
|
|
data:
|
|
type: array
|
|
items:
|
|
$ref: "#/components/schemas/Package"
|
|
example:
|
|
data:
|
|
- slug: quark
|
|
name: Quark
|
|
version: 2.0.4
|
|
type: theme
|
|
enabled: true
|
|
update_available: false
|
|
available_version: null
|
|
author: Team Grav
|
|
description: Quark is the default theme for Grav CMS.
|
|
"401":
|
|
$ref: "#/components/responses/Unauthorized"
|
|
"403":
|
|
$ref: "#/components/responses/Forbidden"
|
|
"429":
|
|
$ref: "#/components/responses/TooManyRequests"
|
|
|
|
/gpm/themes/{slug}:
|
|
get:
|
|
operationId: getInstalledTheme
|
|
tags: [GPM]
|
|
summary: Get installed theme details
|
|
description: |
|
|
Return detailed information about a specific installed theme
|
|
including its configuration blueprint and README.
|
|
parameters:
|
|
- name: slug
|
|
in: path
|
|
required: true
|
|
description: Theme slug (e.g. `quark`).
|
|
schema:
|
|
type: string
|
|
example: quark
|
|
responses:
|
|
"200":
|
|
description: Theme details returned successfully.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
properties:
|
|
data:
|
|
$ref: "#/components/schemas/Package"
|
|
"401":
|
|
$ref: "#/components/responses/Unauthorized"
|
|
"403":
|
|
$ref: "#/components/responses/Forbidden"
|
|
"404":
|
|
$ref: "#/components/responses/NotFound"
|
|
"429":
|
|
$ref: "#/components/responses/TooManyRequests"
|
|
|
|
/gpm/plugins/{slug}/readme:
|
|
get:
|
|
operationId: getPluginReadme
|
|
tags: [GPM]
|
|
summary: Get plugin README content
|
|
description: |
|
|
Return the raw Markdown content of the specified plugin's README.md
|
|
file.
|
|
parameters:
|
|
- name: slug
|
|
in: path
|
|
required: true
|
|
description: Plugin slug (e.g. `admin`, `api`).
|
|
schema:
|
|
type: string
|
|
example: api
|
|
responses:
|
|
"200":
|
|
description: Plugin README content returned.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
properties:
|
|
data:
|
|
type: object
|
|
properties:
|
|
content:
|
|
type: string
|
|
description: Raw Markdown content of the README file.
|
|
example: "# API Plugin\n\nRESTful API for Grav CMS."
|
|
example:
|
|
data:
|
|
content: "# API Plugin\n\nRESTful API for Grav CMS."
|
|
"401":
|
|
$ref: "#/components/responses/Unauthorized"
|
|
"403":
|
|
$ref: "#/components/responses/Forbidden"
|
|
"404":
|
|
$ref: "#/components/responses/NotFound"
|
|
"429":
|
|
$ref: "#/components/responses/TooManyRequests"
|
|
|
|
/gpm/plugins/{slug}/changelog:
|
|
get:
|
|
operationId: getPluginChangelog
|
|
tags: [GPM]
|
|
summary: Get plugin changelog content
|
|
description: |
|
|
Return the raw Markdown content of the specified plugin's CHANGELOG.md
|
|
file.
|
|
parameters:
|
|
- name: slug
|
|
in: path
|
|
required: true
|
|
description: Plugin slug (e.g. `admin`, `api`).
|
|
schema:
|
|
type: string
|
|
example: api
|
|
responses:
|
|
"200":
|
|
description: Plugin changelog content returned.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
properties:
|
|
data:
|
|
type: object
|
|
properties:
|
|
content:
|
|
type: string
|
|
description: Raw Markdown content of the CHANGELOG file.
|
|
example: "# v1.0.0\n\n- Initial release"
|
|
example:
|
|
data:
|
|
content: "# v1.0.0\n\n- Initial release"
|
|
"401":
|
|
$ref: "#/components/responses/Unauthorized"
|
|
"403":
|
|
$ref: "#/components/responses/Forbidden"
|
|
"404":
|
|
$ref: "#/components/responses/NotFound"
|
|
"429":
|
|
$ref: "#/components/responses/TooManyRequests"
|
|
|
|
/gpm/themes/{slug}/readme:
|
|
get:
|
|
operationId: getThemeReadme
|
|
tags: [GPM]
|
|
summary: Get theme README content
|
|
description: |
|
|
Return the raw Markdown content of the specified theme's README.md
|
|
file.
|
|
parameters:
|
|
- name: slug
|
|
in: path
|
|
required: true
|
|
description: Theme slug (e.g. `quark`).
|
|
schema:
|
|
type: string
|
|
example: quark
|
|
responses:
|
|
"200":
|
|
description: Theme README content returned.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
properties:
|
|
data:
|
|
type: object
|
|
properties:
|
|
content:
|
|
type: string
|
|
description: Raw Markdown content of the README file.
|
|
example: "# Quark Theme\n\nDefault theme for Grav CMS."
|
|
example:
|
|
data:
|
|
content: "# Quark Theme\n\nDefault theme for Grav CMS."
|
|
"401":
|
|
$ref: "#/components/responses/Unauthorized"
|
|
"403":
|
|
$ref: "#/components/responses/Forbidden"
|
|
"404":
|
|
$ref: "#/components/responses/NotFound"
|
|
"429":
|
|
$ref: "#/components/responses/TooManyRequests"
|
|
|
|
/gpm/themes/{slug}/changelog:
|
|
get:
|
|
operationId: getThemeChangelog
|
|
tags: [GPM]
|
|
summary: Get theme changelog content
|
|
description: |
|
|
Return the raw Markdown content of the specified theme's CHANGELOG.md
|
|
file.
|
|
parameters:
|
|
- name: slug
|
|
in: path
|
|
required: true
|
|
description: Theme slug (e.g. `quark`).
|
|
schema:
|
|
type: string
|
|
example: quark
|
|
responses:
|
|
"200":
|
|
description: Theme changelog content returned.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
properties:
|
|
data:
|
|
type: object
|
|
properties:
|
|
content:
|
|
type: string
|
|
description: Raw Markdown content of the CHANGELOG file.
|
|
example: "# v2.0.4\n\n- Bug fixes"
|
|
example:
|
|
data:
|
|
content: "# v2.0.4\n\n- Bug fixes"
|
|
"401":
|
|
$ref: "#/components/responses/Unauthorized"
|
|
"403":
|
|
$ref: "#/components/responses/Forbidden"
|
|
"404":
|
|
$ref: "#/components/responses/NotFound"
|
|
"429":
|
|
$ref: "#/components/responses/TooManyRequests"
|
|
|
|
/gpm/updates:
|
|
get:
|
|
operationId: checkUpdates
|
|
tags: [GPM]
|
|
summary: Check for updates
|
|
description: |
|
|
Check the GPM repository for available updates to Grav core,
|
|
installed plugins, and installed themes.
|
|
responses:
|
|
"200":
|
|
description: Update check completed.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
properties:
|
|
data:
|
|
type: object
|
|
properties:
|
|
grav:
|
|
type: object
|
|
nullable: true
|
|
description: Grav core update info, or `null` if up to date.
|
|
properties:
|
|
current:
|
|
type: string
|
|
example: 1.7.48
|
|
available:
|
|
type: string
|
|
example: 1.7.49
|
|
plugins:
|
|
type: array
|
|
items:
|
|
type: object
|
|
properties:
|
|
slug:
|
|
type: string
|
|
example: admin
|
|
current:
|
|
type: string
|
|
example: 1.10.44
|
|
available:
|
|
type: string
|
|
example: 1.10.45
|
|
themes:
|
|
type: array
|
|
items:
|
|
type: object
|
|
properties:
|
|
slug:
|
|
type: string
|
|
example: quark
|
|
current:
|
|
type: string
|
|
example: 2.0.4
|
|
available:
|
|
type: string
|
|
example: 2.0.5
|
|
total:
|
|
type: integer
|
|
description: Total number of available updates.
|
|
example: 3
|
|
installed:
|
|
type: object
|
|
properties:
|
|
plugins:
|
|
type: integer
|
|
example: 12
|
|
themes:
|
|
type: integer
|
|
example: 2
|
|
example:
|
|
data:
|
|
grav:
|
|
current: 1.7.48
|
|
available: 1.7.49
|
|
plugins:
|
|
- slug: admin
|
|
current: 1.10.44
|
|
available: 1.10.45
|
|
themes: []
|
|
total: 2
|
|
installed:
|
|
plugins: 12
|
|
themes: 2
|
|
"401":
|
|
$ref: "#/components/responses/Unauthorized"
|
|
"403":
|
|
$ref: "#/components/responses/Forbidden"
|
|
"429":
|
|
$ref: "#/components/responses/TooManyRequests"
|
|
|
|
/gpm/install:
|
|
post:
|
|
operationId: installPackage
|
|
tags: [GPM]
|
|
summary: Install a package
|
|
description: |
|
|
Install a plugin or theme from the GPM repository. Requires
|
|
`api.gpm.write` permission.
|
|
|
|
For premium packages, include a `license` field or ensure the license
|
|
has already been registered via the license-manager plugin/API.
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
required: [package, type]
|
|
properties:
|
|
package:
|
|
type: string
|
|
description: Package slug to install.
|
|
example: shortcode-core
|
|
type:
|
|
type: string
|
|
enum: [plugin, theme]
|
|
description: Package type.
|
|
example: plugin
|
|
license:
|
|
type: string
|
|
description: |
|
|
License key for premium packages. Format: `XXXXXXXX-XXXXXXXX-XXXXXXXX-XXXXXXXX`.
|
|
If omitted, the system checks `user://data/licenses.yaml` for an existing license.
|
|
example: A1B2C3D4-E5F6A7B8-C9D0E1F2-A3B4C5D6
|
|
example:
|
|
package: shortcode-core
|
|
type: plugin
|
|
responses:
|
|
"200":
|
|
description: Package installed successfully.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
properties:
|
|
data:
|
|
$ref: "#/components/schemas/Package"
|
|
"401":
|
|
$ref: "#/components/responses/Unauthorized"
|
|
"403":
|
|
$ref: "#/components/responses/Forbidden"
|
|
"404":
|
|
$ref: "#/components/responses/NotFound"
|
|
"422":
|
|
$ref: "#/components/responses/ValidationError"
|
|
"429":
|
|
$ref: "#/components/responses/TooManyRequests"
|
|
|
|
/gpm/remove:
|
|
post:
|
|
operationId: removePackage
|
|
tags: [GPM]
|
|
summary: Remove a package
|
|
description: |
|
|
Remove an installed plugin or theme. Requires super admin
|
|
permissions.
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
required: [package]
|
|
properties:
|
|
package:
|
|
type: string
|
|
description: Package slug to remove.
|
|
example: shortcode-core
|
|
example:
|
|
package: shortcode-core
|
|
responses:
|
|
"200":
|
|
description: Package removed successfully.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
properties:
|
|
data:
|
|
type: object
|
|
properties:
|
|
package:
|
|
type: string
|
|
example: shortcode-core
|
|
message:
|
|
type: string
|
|
example: Plugin shortcode-core removed successfully.
|
|
"401":
|
|
$ref: "#/components/responses/Unauthorized"
|
|
"403":
|
|
$ref: "#/components/responses/Forbidden"
|
|
"404":
|
|
$ref: "#/components/responses/NotFound"
|
|
"422":
|
|
$ref: "#/components/responses/ValidationError"
|
|
"429":
|
|
$ref: "#/components/responses/TooManyRequests"
|
|
|
|
/gpm/update:
|
|
post:
|
|
operationId: updatePackage
|
|
tags: [GPM]
|
|
summary: Update a package
|
|
description: |
|
|
Update a single installed plugin or theme to the latest version.
|
|
Requires super admin permissions.
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
required: [package]
|
|
properties:
|
|
package:
|
|
type: string
|
|
description: Package slug to update.
|
|
example: admin
|
|
example:
|
|
package: admin
|
|
responses:
|
|
"200":
|
|
description: Package updated successfully.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
properties:
|
|
data:
|
|
$ref: "#/components/schemas/Package"
|
|
"401":
|
|
$ref: "#/components/responses/Unauthorized"
|
|
"403":
|
|
$ref: "#/components/responses/Forbidden"
|
|
"404":
|
|
$ref: "#/components/responses/NotFound"
|
|
"422":
|
|
$ref: "#/components/responses/ValidationError"
|
|
"429":
|
|
$ref: "#/components/responses/TooManyRequests"
|
|
|
|
/gpm/update-all:
|
|
post:
|
|
operationId: updateAllPackages
|
|
tags: [GPM]
|
|
summary: Update all packages
|
|
description: |
|
|
Update all installed plugins and themes that have available updates.
|
|
Requires super admin permissions.
|
|
responses:
|
|
"200":
|
|
description: All packages updated.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
properties:
|
|
data:
|
|
type: object
|
|
properties:
|
|
updated:
|
|
type: array
|
|
items:
|
|
type: string
|
|
description: Slugs of packages that were updated.
|
|
example: [admin, form, email]
|
|
failed:
|
|
type: array
|
|
items:
|
|
type: string
|
|
description: Slugs of packages that failed to update.
|
|
example: []
|
|
total:
|
|
type: integer
|
|
example: 3
|
|
"401":
|
|
$ref: "#/components/responses/Unauthorized"
|
|
"403":
|
|
$ref: "#/components/responses/Forbidden"
|
|
"429":
|
|
$ref: "#/components/responses/TooManyRequests"
|
|
|
|
/gpm/upgrade:
|
|
post:
|
|
operationId: upgradeGrav
|
|
tags: [GPM]
|
|
summary: Self-upgrade Grav core
|
|
description: |
|
|
Upgrade Grav to the latest available version. Requires super admin
|
|
permissions.
|
|
responses:
|
|
"200":
|
|
description: Grav upgraded successfully.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
properties:
|
|
data:
|
|
type: object
|
|
properties:
|
|
previous_version:
|
|
type: string
|
|
example: 1.7.48
|
|
current_version:
|
|
type: string
|
|
example: 1.7.49
|
|
message:
|
|
type: string
|
|
example: Grav upgraded successfully to 1.7.49.
|
|
"401":
|
|
$ref: "#/components/responses/Unauthorized"
|
|
"403":
|
|
$ref: "#/components/responses/Forbidden"
|
|
"422":
|
|
$ref: "#/components/responses/ValidationError"
|
|
"429":
|
|
$ref: "#/components/responses/TooManyRequests"
|
|
|
|
/gpm/direct-install:
|
|
post:
|
|
operationId: directInstallPackage
|
|
tags: [GPM]
|
|
summary: Install from URL or file upload
|
|
description: |
|
|
Install a plugin or theme directly from a URL or an uploaded ZIP
|
|
file. This bypasses the GPM repository and allows installing custom
|
|
or development packages. Requires super admin permissions.
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
required: [url]
|
|
properties:
|
|
url:
|
|
type: string
|
|
format: uri
|
|
description: URL pointing to a ZIP archive of the package.
|
|
example: https://github.com/user/grav-plugin-custom/archive/main.zip
|
|
example:
|
|
url: https://github.com/user/grav-plugin-custom/archive/main.zip
|
|
multipart/form-data:
|
|
schema:
|
|
type: object
|
|
properties:
|
|
file:
|
|
type: string
|
|
format: binary
|
|
description: ZIP archive to install.
|
|
responses:
|
|
"200":
|
|
description: Package installed from direct source.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
properties:
|
|
data:
|
|
type: object
|
|
properties:
|
|
package:
|
|
type: string
|
|
example: custom-plugin
|
|
type:
|
|
type: string
|
|
example: plugin
|
|
message:
|
|
type: string
|
|
example: Package installed successfully from URL.
|
|
"401":
|
|
$ref: "#/components/responses/Unauthorized"
|
|
"403":
|
|
$ref: "#/components/responses/Forbidden"
|
|
"422":
|
|
$ref: "#/components/responses/ValidationError"
|
|
"429":
|
|
$ref: "#/components/responses/TooManyRequests"
|
|
|
|
/gpm/search:
|
|
get:
|
|
operationId: searchPackages
|
|
tags: [GPM]
|
|
summary: Search GPM repository
|
|
description: |
|
|
Search across all plugins and themes in the GPM repository by keyword.
|
|
Matches against slug, name, description, author, and keywords.
|
|
parameters:
|
|
- name: q
|
|
in: query
|
|
required: true
|
|
description: Search query string.
|
|
schema:
|
|
type: string
|
|
example: email
|
|
- $ref: "#/components/parameters/page"
|
|
- $ref: "#/components/parameters/per_page"
|
|
responses:
|
|
"200":
|
|
description: Paginated search results across plugins and themes.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
allOf:
|
|
- $ref: "#/components/schemas/PaginatedEnvelope"
|
|
- type: object
|
|
properties:
|
|
data:
|
|
type: array
|
|
items:
|
|
$ref: "#/components/schemas/Package"
|
|
"401":
|
|
$ref: "#/components/responses/Unauthorized"
|
|
"403":
|
|
$ref: "#/components/responses/Forbidden"
|
|
"422":
|
|
$ref: "#/components/responses/ValidationError"
|
|
"429":
|
|
$ref: "#/components/responses/TooManyRequests"
|
|
|
|
/gpm/repository/plugins:
|
|
get:
|
|
operationId: browseRepositoryPlugins
|
|
tags: [GPM]
|
|
summary: Browse available plugins
|
|
description: |
|
|
Return a paginated list of plugins available in the GPM repository.
|
|
Includes metadata about each package and whether it is already
|
|
installed. Use the `q` parameter to filter by keyword.
|
|
parameters:
|
|
- name: q
|
|
in: query
|
|
required: false
|
|
description: Filter by keyword (matches slug, name, description, author, keywords).
|
|
schema:
|
|
type: string
|
|
- $ref: "#/components/parameters/page"
|
|
- $ref: "#/components/parameters/per_page"
|
|
responses:
|
|
"200":
|
|
description: Paginated list of available plugins.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
allOf:
|
|
- $ref: "#/components/schemas/PaginatedEnvelope"
|
|
- type: object
|
|
properties:
|
|
data:
|
|
type: array
|
|
items:
|
|
$ref: "#/components/schemas/Package"
|
|
"401":
|
|
$ref: "#/components/responses/Unauthorized"
|
|
"403":
|
|
$ref: "#/components/responses/Forbidden"
|
|
"429":
|
|
$ref: "#/components/responses/TooManyRequests"
|
|
|
|
/gpm/repository/themes:
|
|
get:
|
|
operationId: browseRepositoryThemes
|
|
tags: [GPM]
|
|
summary: Browse available themes
|
|
description: |
|
|
Return a paginated list of themes available in the GPM repository.
|
|
Includes metadata about each theme and whether it is already
|
|
installed. Use the `q` parameter to filter by keyword.
|
|
parameters:
|
|
- name: q
|
|
in: query
|
|
required: false
|
|
description: Filter by keyword (matches slug, name, description, author, keywords).
|
|
schema:
|
|
type: string
|
|
- $ref: "#/components/parameters/page"
|
|
- $ref: "#/components/parameters/per_page"
|
|
responses:
|
|
"200":
|
|
description: Paginated list of available themes.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
allOf:
|
|
- $ref: "#/components/schemas/PaginatedEnvelope"
|
|
- type: object
|
|
properties:
|
|
data:
|
|
type: array
|
|
items:
|
|
$ref: "#/components/schemas/Package"
|
|
"401":
|
|
$ref: "#/components/responses/Unauthorized"
|
|
"403":
|
|
$ref: "#/components/responses/Forbidden"
|
|
"429":
|
|
$ref: "#/components/responses/TooManyRequests"
|
|
|
|
/gpm/repository/{slug}:
|
|
get:
|
|
operationId: getRepositoryPackage
|
|
tags: [GPM]
|
|
summary: Get repository package details
|
|
description: |
|
|
Return detailed information about a package available in the GPM
|
|
repository, including its README, changelog, and screenshots.
|
|
parameters:
|
|
- name: slug
|
|
in: path
|
|
required: true
|
|
description: Package slug in the repository.
|
|
schema:
|
|
type: string
|
|
example: admin
|
|
responses:
|
|
"200":
|
|
description: Repository package details returned successfully.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
properties:
|
|
data:
|
|
$ref: "#/components/schemas/Package"
|
|
"401":
|
|
$ref: "#/components/responses/Unauthorized"
|
|
"403":
|
|
$ref: "#/components/responses/Forbidden"
|
|
"404":
|
|
$ref: "#/components/responses/NotFound"
|
|
"429":
|
|
$ref: "#/components/responses/TooManyRequests"
|
|
|
|
/gpm/plugins/{slug}/page:
|
|
get:
|
|
operationId: getPluginPage
|
|
tags: [GPM]
|
|
summary: Get plugin admin page definition
|
|
description: |
|
|
Returns the admin page definition for a plugin that registers a custom
|
|
page in the admin sidebar. The response includes the page metadata,
|
|
blueprint reference, data and save endpoints, available actions, and
|
|
whether the plugin provides a custom web component.
|
|
parameters:
|
|
- name: slug
|
|
in: path
|
|
required: true
|
|
description: Plugin slug (e.g. `admin-next`, `api`).
|
|
schema:
|
|
type: string
|
|
example: api
|
|
responses:
|
|
"200":
|
|
description: Plugin page definition returned.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
properties:
|
|
data:
|
|
$ref: "#/components/schemas/PluginPageDefinition"
|
|
example:
|
|
data:
|
|
id: api-settings
|
|
plugin: api
|
|
title: API Settings
|
|
icon: plug
|
|
page_type: blueprint-form
|
|
blueprint: /blueprints/plugins/api/pages/settings
|
|
data_endpoint: /config/plugins/api
|
|
save_endpoint: /config/plugins/api
|
|
actions:
|
|
- save
|
|
has_custom_component: false
|
|
"401":
|
|
$ref: "#/components/responses/Unauthorized"
|
|
"403":
|
|
$ref: "#/components/responses/Forbidden"
|
|
"404":
|
|
description: No admin page found for this plugin.
|
|
content:
|
|
application/problem+json:
|
|
schema:
|
|
$ref: "#/components/schemas/ErrorResponse"
|
|
example:
|
|
status: 404
|
|
title: Not Found
|
|
detail: "No admin page registered for plugin: api"
|
|
"429":
|
|
$ref: "#/components/responses/TooManyRequests"
|
|
|
|
/gpm/plugins/{slug}/page-script:
|
|
get:
|
|
operationId: getPluginPageScript
|
|
tags: [GPM]
|
|
summary: Get plugin page web component script
|
|
description: |
|
|
Serves the JavaScript file containing the plugin's page-level web
|
|
component. This is used by the admin panel to dynamically load
|
|
plugin-provided custom page UIs. Returns `application/javascript`
|
|
content type.
|
|
parameters:
|
|
- name: slug
|
|
in: path
|
|
required: true
|
|
description: Plugin slug (e.g. `admin-next`).
|
|
schema:
|
|
type: string
|
|
example: admin-next
|
|
responses:
|
|
"200":
|
|
description: JavaScript file returned.
|
|
content:
|
|
application/javascript:
|
|
schema:
|
|
type: string
|
|
description: Raw JavaScript source for the plugin's web component.
|
|
"401":
|
|
$ref: "#/components/responses/Unauthorized"
|
|
"403":
|
|
$ref: "#/components/responses/Forbidden"
|
|
"404":
|
|
description: No page script found for this plugin.
|
|
content:
|
|
application/problem+json:
|
|
schema:
|
|
$ref: "#/components/schemas/ErrorResponse"
|
|
example:
|
|
status: 404
|
|
title: Not Found
|
|
detail: "No page script found for plugin: admin-next"
|
|
"429":
|
|
$ref: "#/components/responses/TooManyRequests"
|
|
|
|
/gpm/plugins/{slug}/report-script/{reportId}:
|
|
get:
|
|
operationId: getPluginReportScript
|
|
tags: [GPM]
|
|
summary: Get plugin report web component script
|
|
description: |
|
|
Serves the JavaScript file containing a plugin's report web component.
|
|
Used by the admin panel to dynamically load plugin-provided report UIs
|
|
in the Tools > Reports tab. Returns `application/javascript` content.
|
|
|
|
Convention: plugins place report scripts at
|
|
`admin-next/reports/{reportId}.js` within their plugin directory.
|
|
parameters:
|
|
- name: slug
|
|
in: path
|
|
required: true
|
|
description: Plugin slug (e.g. `problems`).
|
|
schema:
|
|
type: string
|
|
example: problems
|
|
- name: reportId
|
|
in: path
|
|
required: true
|
|
description: Report component identifier matching the `component` field from `/reports`.
|
|
schema:
|
|
type: string
|
|
example: problems-report
|
|
responses:
|
|
"200":
|
|
description: JavaScript file returned.
|
|
content:
|
|
application/javascript:
|
|
schema:
|
|
type: string
|
|
description: Raw JavaScript source for the report web component.
|
|
"401":
|
|
$ref: "#/components/responses/Unauthorized"
|
|
"403":
|
|
$ref: "#/components/responses/Forbidden"
|
|
"404":
|
|
description: No report script found.
|
|
content:
|
|
application/problem+json:
|
|
schema:
|
|
$ref: "#/components/schemas/ErrorResponse"
|
|
example:
|
|
status: 404
|
|
title: Not Found
|
|
detail: "Report component 'problems-report' not found for plugin 'problems'."
|
|
"429":
|
|
$ref: "#/components/responses/TooManyRequests"
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Scheduler
|
|
# ---------------------------------------------------------------------------
|
|
/scheduler/jobs:
|
|
get:
|
|
operationId: listSchedulerJobs
|
|
tags: [Scheduler]
|
|
summary: List scheduler jobs
|
|
description: |
|
|
Return all configured scheduler jobs with their cron expressions,
|
|
enabled state, last run time, and error status.
|
|
responses:
|
|
"200":
|
|
description: List of scheduler jobs.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
properties:
|
|
data:
|
|
type: array
|
|
items:
|
|
$ref: "#/components/schemas/Job"
|
|
example:
|
|
data:
|
|
- id: default-site-backup
|
|
command: Grav\Common\Backup\Backups::backup
|
|
expression: "0 3 * * *"
|
|
enabled: true
|
|
status: success
|
|
last_run: "2026-03-26T03:00:00+00:00"
|
|
error: null
|
|
"401":
|
|
$ref: "#/components/responses/Unauthorized"
|
|
"403":
|
|
$ref: "#/components/responses/Forbidden"
|
|
"429":
|
|
$ref: "#/components/responses/TooManyRequests"
|
|
|
|
/scheduler/status:
|
|
get:
|
|
operationId: getSchedulerStatus
|
|
tags: [Scheduler]
|
|
summary: Get cron status
|
|
description: |
|
|
Return the current status of the system cron integration, including
|
|
whether the cron tab entry is properly installed and the commands
|
|
used.
|
|
responses:
|
|
"200":
|
|
description: Scheduler status returned.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
properties:
|
|
data:
|
|
type: object
|
|
properties:
|
|
crontab_status:
|
|
type: string
|
|
description: Whether the crontab entry is installed.
|
|
enum: [installed, not_installed, error]
|
|
example: installed
|
|
cron_command:
|
|
type: string
|
|
description: The cron command as it should appear in the crontab.
|
|
example: "* * * * * cd /var/www/grav && bin/grav scheduler 1>> /dev/null 2>&1"
|
|
scheduler_command:
|
|
type: string
|
|
description: The Grav scheduler CLI command.
|
|
example: bin/grav scheduler
|
|
whoami:
|
|
type: string
|
|
description: System user running the cron process.
|
|
example: www-data
|
|
"401":
|
|
$ref: "#/components/responses/Unauthorized"
|
|
"403":
|
|
$ref: "#/components/responses/Forbidden"
|
|
"429":
|
|
$ref: "#/components/responses/TooManyRequests"
|
|
|
|
/scheduler/history:
|
|
get:
|
|
operationId: getSchedulerHistory
|
|
tags: [Scheduler]
|
|
summary: Get job execution history
|
|
description: |
|
|
Return a paginated list of past scheduler job runs, ordered by
|
|
execution time (newest first).
|
|
parameters:
|
|
- $ref: "#/components/parameters/page"
|
|
- $ref: "#/components/parameters/per_page"
|
|
responses:
|
|
"200":
|
|
description: Paginated job execution history.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
allOf:
|
|
- $ref: "#/components/schemas/PaginatedEnvelope"
|
|
- type: object
|
|
properties:
|
|
data:
|
|
type: array
|
|
items:
|
|
type: object
|
|
properties:
|
|
id:
|
|
type: string
|
|
example: default-site-backup
|
|
command:
|
|
type: string
|
|
example: Grav\Common\Backup\Backups::backup
|
|
started:
|
|
type: string
|
|
format: date-time
|
|
example: "2026-03-26T03:00:00+00:00"
|
|
completed:
|
|
type: string
|
|
format: date-time
|
|
example: "2026-03-26T03:00:45+00:00"
|
|
status:
|
|
type: string
|
|
enum: [success, error]
|
|
example: success
|
|
output:
|
|
type: string
|
|
nullable: true
|
|
example: Backup created successfully.
|
|
"401":
|
|
$ref: "#/components/responses/Unauthorized"
|
|
"403":
|
|
$ref: "#/components/responses/Forbidden"
|
|
"429":
|
|
$ref: "#/components/responses/TooManyRequests"
|
|
|
|
/scheduler/run:
|
|
post:
|
|
operationId: triggerSchedulerRun
|
|
tags: [Scheduler]
|
|
summary: Trigger a scheduler run
|
|
description: |
|
|
Trigger the scheduler to process all due jobs immediately. Optionally
|
|
force all jobs to run regardless of their schedule. Requires super
|
|
admin permissions.
|
|
requestBody:
|
|
required: false
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
properties:
|
|
force:
|
|
type: boolean
|
|
description: When `true`, run all jobs regardless of schedule.
|
|
default: false
|
|
example:
|
|
force: false
|
|
responses:
|
|
"200":
|
|
description: Scheduler run completed.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
properties:
|
|
data:
|
|
type: object
|
|
properties:
|
|
jobs_run:
|
|
type: integer
|
|
description: Number of jobs that were executed.
|
|
example: 2
|
|
results:
|
|
type: array
|
|
items:
|
|
type: object
|
|
properties:
|
|
id:
|
|
type: string
|
|
example: default-site-backup
|
|
status:
|
|
type: string
|
|
enum: [success, error, skipped]
|
|
example: success
|
|
message:
|
|
type: string
|
|
nullable: true
|
|
example: null
|
|
"401":
|
|
$ref: "#/components/responses/Unauthorized"
|
|
"403":
|
|
$ref: "#/components/responses/Forbidden"
|
|
"429":
|
|
$ref: "#/components/responses/TooManyRequests"
|
|
|
|
/systeminfo:
|
|
get:
|
|
operationId: getSystemInfo
|
|
tags: [System]
|
|
summary: Get system info overview
|
|
description: |
|
|
Return a system info overview including PHP info, Grav status, disk
|
|
usage, plugin health, and cache status.
|
|
responses:
|
|
"200":
|
|
description: System info returned.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
properties:
|
|
data:
|
|
type: object
|
|
properties:
|
|
php:
|
|
type: object
|
|
description: PHP runtime details and extension status.
|
|
additionalProperties: true
|
|
grav:
|
|
type: object
|
|
description: Grav version and health info.
|
|
additionalProperties: true
|
|
disk:
|
|
type: object
|
|
description: Disk usage information.
|
|
properties:
|
|
total:
|
|
type: integer
|
|
description: Total disk space in bytes.
|
|
example: 53687091200
|
|
free:
|
|
type: integer
|
|
description: Free disk space in bytes.
|
|
example: 21474836480
|
|
used_percent:
|
|
type: number
|
|
format: float
|
|
example: 60.0
|
|
plugins:
|
|
type: object
|
|
description: Plugin health summary.
|
|
additionalProperties: true
|
|
cache:
|
|
type: object
|
|
description: Cache driver and status.
|
|
additionalProperties: true
|
|
"401":
|
|
$ref: "#/components/responses/Unauthorized"
|
|
"403":
|
|
$ref: "#/components/responses/Forbidden"
|
|
"429":
|
|
$ref: "#/components/responses/TooManyRequests"
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Reports
|
|
# ---------------------------------------------------------------------------
|
|
/reports:
|
|
get:
|
|
operationId: getReports
|
|
tags: [Reports]
|
|
summary: Get plugin-extensible reports
|
|
description: |
|
|
Returns an array of diagnostic reports. Built-in reports include
|
|
a Security Check (XSS scan) and a YAML Linter. Plugins can add
|
|
their own reports via the `onApiGenerateReports` event, optionally
|
|
specifying a web component for custom rendering.
|
|
responses:
|
|
"200":
|
|
description: Reports returned.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
properties:
|
|
data:
|
|
type: array
|
|
items:
|
|
type: object
|
|
properties:
|
|
id:
|
|
type: string
|
|
description: Unique report identifier.
|
|
example: security-check
|
|
title:
|
|
type: string
|
|
description: Human-readable report title.
|
|
example: Grav Security Check
|
|
provider:
|
|
type: string
|
|
description: Source of the report (core or plugin name).
|
|
example: core
|
|
component:
|
|
type: string
|
|
nullable: true
|
|
description: Optional web component tag name for custom rendering.
|
|
example: null
|
|
status:
|
|
type: string
|
|
enum: [success, warning, error]
|
|
description: Overall report status.
|
|
example: success
|
|
message:
|
|
type: string
|
|
description: Summary message for the report.
|
|
example: "Security Scan complete: No issues found."
|
|
items:
|
|
type: array
|
|
description: Report-specific detail items.
|
|
items:
|
|
type: object
|
|
additionalProperties: true
|
|
"401":
|
|
$ref: "#/components/responses/Unauthorized"
|
|
"403":
|
|
$ref: "#/components/responses/Forbidden"
|
|
"429":
|
|
$ref: "#/components/responses/TooManyRequests"
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Sidebar
|
|
# ---------------------------------------------------------------------------
|
|
/sidebar/items:
|
|
get:
|
|
operationId: getSidebarItems
|
|
tags: [Sidebar]
|
|
summary: Get admin sidebar navigation items
|
|
description: |
|
|
Returns an array of sidebar navigation items contributed by installed
|
|
plugins. Each item defines a route, label, icon, and optional badge
|
|
for rendering in the admin sidebar.
|
|
responses:
|
|
"200":
|
|
description: Sidebar items returned successfully.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
properties:
|
|
data:
|
|
type: array
|
|
items:
|
|
$ref: "#/components/schemas/SidebarItem"
|
|
example:
|
|
data:
|
|
- id: dashboard
|
|
plugin: admin-next
|
|
label: Dashboard
|
|
icon: gauge
|
|
route: /dashboard
|
|
priority: 100
|
|
badge: null
|
|
- id: pages
|
|
plugin: admin-next
|
|
label: Pages
|
|
icon: file-text
|
|
route: /pages
|
|
priority: 90
|
|
badge: null
|
|
"401":
|
|
$ref: "#/components/responses/Unauthorized"
|
|
"403":
|
|
$ref: "#/components/responses/Forbidden"
|
|
"429":
|
|
$ref: "#/components/responses/TooManyRequests"
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Dashboard
|
|
# ---------------------------------------------------------------------------
|
|
/dashboard/notifications:
|
|
get:
|
|
operationId: getDashboardNotifications
|
|
tags: [Dashboard]
|
|
summary: Get notifications
|
|
description: |
|
|
Return current notifications for the dashboard. Notifications may
|
|
come from Grav core, plugin updates, or security advisories.
|
|
parameters:
|
|
- name: location
|
|
in: query
|
|
description: Filter notifications by location (e.g. `dashboard`, `plugins`).
|
|
schema:
|
|
type: string
|
|
example: dashboard
|
|
- name: force
|
|
in: query
|
|
description: When `true`, bypass the notification cache and fetch fresh data.
|
|
schema:
|
|
type: boolean
|
|
default: false
|
|
responses:
|
|
"200":
|
|
description: Notifications returned.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
properties:
|
|
data:
|
|
type: array
|
|
items:
|
|
$ref: "#/components/schemas/Notification"
|
|
example:
|
|
data:
|
|
- id: grav-update-1.7.49
|
|
type: info
|
|
message: Grav 1.7.49 is now available.
|
|
date: "2026-03-25T10:00:00+00:00"
|
|
location: dashboard
|
|
link: https://getgrav.org/blog/grav-1.7.49
|
|
read: false
|
|
"401":
|
|
$ref: "#/components/responses/Unauthorized"
|
|
"403":
|
|
$ref: "#/components/responses/Forbidden"
|
|
"429":
|
|
$ref: "#/components/responses/TooManyRequests"
|
|
|
|
/dashboard/notifications/{id}/hide:
|
|
post:
|
|
operationId: hideNotification
|
|
tags: [Dashboard]
|
|
summary: Dismiss a notification
|
|
description: |
|
|
Mark a notification as hidden/dismissed so it no longer appears in
|
|
the dashboard feed.
|
|
parameters:
|
|
- name: id
|
|
in: path
|
|
required: true
|
|
description: Notification ID.
|
|
schema:
|
|
type: string
|
|
example: grav-update-1.7.49
|
|
responses:
|
|
"200":
|
|
description: Notification dismissed.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
properties:
|
|
data:
|
|
type: object
|
|
properties:
|
|
id:
|
|
type: string
|
|
example: grav-update-1.7.49
|
|
hidden:
|
|
type: boolean
|
|
example: true
|
|
"401":
|
|
$ref: "#/components/responses/Unauthorized"
|
|
"403":
|
|
$ref: "#/components/responses/Forbidden"
|
|
"404":
|
|
$ref: "#/components/responses/NotFound"
|
|
"429":
|
|
$ref: "#/components/responses/TooManyRequests"
|
|
|
|
/dashboard/feed:
|
|
get:
|
|
operationId: getDashboardFeed
|
|
tags: [Dashboard]
|
|
summary: Get news feed
|
|
description: |
|
|
Return the latest news feed items from the Grav blog and community.
|
|
parameters:
|
|
- name: force
|
|
in: query
|
|
description: When `true`, bypass the feed cache and fetch fresh data.
|
|
schema:
|
|
type: boolean
|
|
default: false
|
|
responses:
|
|
"200":
|
|
description: Feed items returned.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
properties:
|
|
data:
|
|
type: array
|
|
items:
|
|
$ref: "#/components/schemas/FeedItem"
|
|
example:
|
|
data:
|
|
- title: Grav 1.7.49 Released
|
|
url: https://getgrav.org/blog/grav-1.7.49
|
|
date: "2026-03-25T10:00:00+00:00"
|
|
excerpt: This release includes performance improvements and bug fixes.
|
|
"401":
|
|
$ref: "#/components/responses/Unauthorized"
|
|
"403":
|
|
$ref: "#/components/responses/Forbidden"
|
|
"429":
|
|
$ref: "#/components/responses/TooManyRequests"
|
|
|
|
/dashboard/stats:
|
|
get:
|
|
operationId: getDashboardStats
|
|
tags: [Dashboard]
|
|
summary: Get statistics snapshot
|
|
description: |
|
|
Return a summary of site statistics suitable for rendering a
|
|
dashboard overview.
|
|
responses:
|
|
"200":
|
|
description: Dashboard stats returned.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
properties:
|
|
data:
|
|
$ref: "#/components/schemas/DashboardStats"
|
|
example:
|
|
data:
|
|
pages: 42
|
|
users: 5
|
|
plugins: 12
|
|
theme: quark
|
|
grav_version: 1.7.49
|
|
php_version: 8.3.14
|
|
last_backup: "2026-03-26T03:00:00+00:00"
|
|
"401":
|
|
$ref: "#/components/responses/Unauthorized"
|
|
"403":
|
|
$ref: "#/components/responses/Forbidden"
|
|
"429":
|
|
$ref: "#/components/responses/TooManyRequests"
|
|
|
|
/dashboard/popularity:
|
|
get:
|
|
operationId: getDashboardPopularity
|
|
tags: [Dashboard]
|
|
summary: Get page view statistics
|
|
description: |
|
|
Return page view statistics from the admin popularity tracker.
|
|
Includes daily chart data (last 14 days), summary counters
|
|
(today, this week, this month), and top pages by total views.
|
|
responses:
|
|
"200":
|
|
description: Page view statistics.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
properties:
|
|
data:
|
|
type: object
|
|
properties:
|
|
summary:
|
|
type: object
|
|
properties:
|
|
today:
|
|
type: integer
|
|
example: 12
|
|
week:
|
|
type: integer
|
|
example: 84
|
|
month:
|
|
type: integer
|
|
example: 312
|
|
chart:
|
|
type: array
|
|
items:
|
|
type: object
|
|
properties:
|
|
date:
|
|
type: string
|
|
example: "Mar 28"
|
|
views:
|
|
type: integer
|
|
example: 27
|
|
top_pages:
|
|
type: array
|
|
items:
|
|
type: object
|
|
properties:
|
|
route:
|
|
type: string
|
|
example: "/blog"
|
|
views:
|
|
type: integer
|
|
example: 30
|
|
"401":
|
|
$ref: "#/components/responses/Unauthorized"
|
|
"403":
|
|
$ref: "#/components/responses/Forbidden"
|
|
"429":
|
|
$ref: "#/components/responses/TooManyRequests"
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Webhooks
|
|
# ---------------------------------------------------------------------------
|
|
/webhooks:
|
|
get:
|
|
operationId: listWebhooks
|
|
tags: [Webhooks]
|
|
summary: List webhooks
|
|
description: |
|
|
Return all configured outbound webhooks with their event
|
|
subscriptions and enabled state.
|
|
responses:
|
|
"200":
|
|
description: List of webhooks.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
properties:
|
|
data:
|
|
type: array
|
|
items:
|
|
$ref: "#/components/schemas/Webhook"
|
|
example:
|
|
data:
|
|
- id: wh_abc123
|
|
url: https://example.com/hooks/grav
|
|
events:
|
|
- pages.created
|
|
- pages.updated
|
|
enabled: true
|
|
headers: {}
|
|
created: "2026-03-20T12:00:00+00:00"
|
|
last_delivery: "2026-03-25T14:30:00+00:00"
|
|
"401":
|
|
$ref: "#/components/responses/Unauthorized"
|
|
"403":
|
|
$ref: "#/components/responses/Forbidden"
|
|
"429":
|
|
$ref: "#/components/responses/TooManyRequests"
|
|
|
|
post:
|
|
operationId: createWebhook
|
|
tags: [Webhooks]
|
|
summary: Create a webhook
|
|
description: |
|
|
Register a new outbound webhook. The response includes a
|
|
`secret` field that can be used to verify delivery signatures.
|
|
The secret is only returned once at creation time.
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
required: [url]
|
|
properties:
|
|
url:
|
|
type: string
|
|
format: uri
|
|
description: Destination URL for webhook deliveries.
|
|
example: https://example.com/hooks/grav
|
|
events:
|
|
type: array
|
|
items:
|
|
type: string
|
|
description: |
|
|
Event types to subscribe to. If empty or omitted, the
|
|
webhook receives all events.
|
|
example: [pages.created, pages.updated, pages.deleted]
|
|
enabled:
|
|
type: boolean
|
|
description: Whether the webhook is active.
|
|
default: true
|
|
headers:
|
|
type: object
|
|
additionalProperties:
|
|
type: string
|
|
description: Custom HTTP headers to include in deliveries.
|
|
example: {}
|
|
example:
|
|
url: https://example.com/hooks/grav
|
|
events: [pages.created, pages.updated]
|
|
enabled: true
|
|
responses:
|
|
"201":
|
|
description: Webhook created. The `secret` is returned only once.
|
|
headers:
|
|
Location:
|
|
description: URL of the newly created webhook resource.
|
|
schema:
|
|
type: string
|
|
example: /api/v1/webhooks/wh_abc123
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
properties:
|
|
data:
|
|
allOf:
|
|
- $ref: "#/components/schemas/Webhook"
|
|
- type: object
|
|
properties:
|
|
secret:
|
|
type: string
|
|
description: |
|
|
HMAC secret for verifying delivery signatures.
|
|
Store it securely -- it is shown only once.
|
|
example: whsec_a1b2c3d4e5f6g7h8i9j0
|
|
example:
|
|
data:
|
|
id: wh_abc123
|
|
url: https://example.com/hooks/grav
|
|
events: [pages.created, pages.updated]
|
|
enabled: true
|
|
headers: {}
|
|
created: "2026-03-26T12:00:00+00:00"
|
|
last_delivery: null
|
|
secret: whsec_a1b2c3d4e5f6g7h8i9j0
|
|
"401":
|
|
$ref: "#/components/responses/Unauthorized"
|
|
"403":
|
|
$ref: "#/components/responses/Forbidden"
|
|
"422":
|
|
$ref: "#/components/responses/ValidationError"
|
|
"429":
|
|
$ref: "#/components/responses/TooManyRequests"
|
|
|
|
/webhooks/{id}:
|
|
get:
|
|
operationId: getWebhook
|
|
tags: [Webhooks]
|
|
summary: Get a webhook
|
|
description: Retrieve a single webhook by its ID.
|
|
parameters:
|
|
- $ref: "#/components/parameters/webhookId"
|
|
responses:
|
|
"200":
|
|
description: Webhook returned.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
properties:
|
|
data:
|
|
$ref: "#/components/schemas/Webhook"
|
|
"401":
|
|
$ref: "#/components/responses/Unauthorized"
|
|
"403":
|
|
$ref: "#/components/responses/Forbidden"
|
|
"404":
|
|
$ref: "#/components/responses/NotFound"
|
|
"429":
|
|
$ref: "#/components/responses/TooManyRequests"
|
|
|
|
patch:
|
|
operationId: updateWebhook
|
|
tags: [Webhooks]
|
|
summary: Update a webhook
|
|
description: |
|
|
Partially update a webhook. Only the fields included in the request
|
|
body are modified.
|
|
parameters:
|
|
- $ref: "#/components/parameters/webhookId"
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
properties:
|
|
url:
|
|
type: string
|
|
format: uri
|
|
example: https://example.com/hooks/grav-v2
|
|
events:
|
|
type: array
|
|
items:
|
|
type: string
|
|
example: [pages.created, pages.updated, pages.deleted]
|
|
enabled:
|
|
type: boolean
|
|
example: true
|
|
headers:
|
|
type: object
|
|
additionalProperties:
|
|
type: string
|
|
example:
|
|
url: https://example.com/hooks/grav-v2
|
|
events: [pages.created, pages.updated, pages.deleted]
|
|
responses:
|
|
"200":
|
|
description: Webhook updated.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
properties:
|
|
data:
|
|
$ref: "#/components/schemas/Webhook"
|
|
"401":
|
|
$ref: "#/components/responses/Unauthorized"
|
|
"403":
|
|
$ref: "#/components/responses/Forbidden"
|
|
"404":
|
|
$ref: "#/components/responses/NotFound"
|
|
"422":
|
|
$ref: "#/components/responses/ValidationError"
|
|
"429":
|
|
$ref: "#/components/responses/TooManyRequests"
|
|
|
|
delete:
|
|
operationId: deleteWebhook
|
|
tags: [Webhooks]
|
|
summary: Delete a webhook
|
|
description: Permanently remove a webhook and all its delivery history.
|
|
parameters:
|
|
- $ref: "#/components/parameters/webhookId"
|
|
responses:
|
|
"204":
|
|
description: Webhook deleted. No content returned.
|
|
"401":
|
|
$ref: "#/components/responses/Unauthorized"
|
|
"403":
|
|
$ref: "#/components/responses/Forbidden"
|
|
"404":
|
|
$ref: "#/components/responses/NotFound"
|
|
"429":
|
|
$ref: "#/components/responses/TooManyRequests"
|
|
|
|
/webhooks/{id}/deliveries:
|
|
get:
|
|
operationId: listWebhookDeliveries
|
|
tags: [Webhooks]
|
|
summary: List webhook deliveries
|
|
description: |
|
|
Return a paginated log of deliveries for a specific webhook,
|
|
ordered newest first.
|
|
parameters:
|
|
- $ref: "#/components/parameters/webhookId"
|
|
- $ref: "#/components/parameters/page"
|
|
- $ref: "#/components/parameters/per_page"
|
|
responses:
|
|
"200":
|
|
description: Paginated delivery log.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
allOf:
|
|
- $ref: "#/components/schemas/PaginatedEnvelope"
|
|
- type: object
|
|
properties:
|
|
data:
|
|
type: array
|
|
items:
|
|
$ref: "#/components/schemas/WebhookDelivery"
|
|
"401":
|
|
$ref: "#/components/responses/Unauthorized"
|
|
"403":
|
|
$ref: "#/components/responses/Forbidden"
|
|
"404":
|
|
$ref: "#/components/responses/NotFound"
|
|
"429":
|
|
$ref: "#/components/responses/TooManyRequests"
|
|
|
|
/webhooks/{id}/test:
|
|
post:
|
|
operationId: testWebhook
|
|
tags: [Webhooks]
|
|
summary: Send a test delivery
|
|
description: |
|
|
Send a test payload to the webhook's URL to verify connectivity.
|
|
The test event type is `webhook.test`.
|
|
parameters:
|
|
- $ref: "#/components/parameters/webhookId"
|
|
responses:
|
|
"200":
|
|
description: Test delivery sent.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
properties:
|
|
data:
|
|
$ref: "#/components/schemas/WebhookDelivery"
|
|
"401":
|
|
$ref: "#/components/responses/Unauthorized"
|
|
"403":
|
|
$ref: "#/components/responses/Forbidden"
|
|
"404":
|
|
$ref: "#/components/responses/NotFound"
|
|
"429":
|
|
$ref: "#/components/responses/TooManyRequests"
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Blueprints
|
|
# ---------------------------------------------------------------------------
|
|
/blueprints/pages:
|
|
get:
|
|
operationId: listPageTypes
|
|
tags: [Blueprints]
|
|
summary: List available page templates
|
|
description: Returns all page template types available in the active theme.
|
|
responses:
|
|
"200":
|
|
description: List of page types.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
properties:
|
|
data:
|
|
type: array
|
|
items:
|
|
type: object
|
|
properties:
|
|
type:
|
|
type: string
|
|
example: blog
|
|
label:
|
|
type: string
|
|
example: Blog
|
|
"401":
|
|
$ref: "#/components/responses/Unauthorized"
|
|
|
|
/blueprints/pages/{template}:
|
|
get:
|
|
operationId: getPageBlueprint
|
|
tags: [Blueprints]
|
|
summary: Get resolved blueprint for a page template
|
|
description: |
|
|
Returns the fully resolved blueprint for the given page template.
|
|
Inheritance via `extends@` and `import@` directives is resolved
|
|
server-side, producing a complete field tree ready for client-side
|
|
form rendering.
|
|
parameters:
|
|
- name: template
|
|
in: path
|
|
required: true
|
|
schema:
|
|
type: string
|
|
description: Page template name (e.g., `default`, `blog`, `item`)
|
|
example: blog
|
|
responses:
|
|
"200":
|
|
description: Resolved blueprint.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/BlueprintResponse"
|
|
"401":
|
|
$ref: "#/components/responses/Unauthorized"
|
|
"404":
|
|
$ref: "#/components/responses/NotFound"
|
|
|
|
/blueprints/plugins/{plugin}:
|
|
get:
|
|
operationId: getPluginBlueprint
|
|
tags: [Blueprints]
|
|
summary: Get blueprint for a plugin
|
|
description: Returns the configuration blueprint for the specified plugin.
|
|
parameters:
|
|
- name: plugin
|
|
in: path
|
|
required: true
|
|
schema:
|
|
type: string
|
|
description: Plugin slug (directory name)
|
|
example: email
|
|
responses:
|
|
"200":
|
|
description: Plugin blueprint.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/BlueprintResponse"
|
|
"401":
|
|
$ref: "#/components/responses/Unauthorized"
|
|
"404":
|
|
$ref: "#/components/responses/NotFound"
|
|
|
|
/blueprints/themes/{theme}:
|
|
get:
|
|
operationId: getThemeBlueprint
|
|
tags: [Blueprints]
|
|
summary: Get blueprint for a theme
|
|
description: Returns the configuration blueprint for the specified theme.
|
|
parameters:
|
|
- name: theme
|
|
in: path
|
|
required: true
|
|
schema:
|
|
type: string
|
|
description: Theme slug (directory name)
|
|
example: quark
|
|
responses:
|
|
"200":
|
|
description: Theme blueprint.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/BlueprintResponse"
|
|
"401":
|
|
$ref: "#/components/responses/Unauthorized"
|
|
"404":
|
|
$ref: "#/components/responses/NotFound"
|
|
|
|
/blueprints/config/{scope}:
|
|
get:
|
|
operationId: getConfigBlueprint
|
|
tags: [Blueprints]
|
|
summary: Get blueprint for system configuration
|
|
description: Returns the configuration blueprint for the specified scope.
|
|
parameters:
|
|
- name: scope
|
|
in: path
|
|
required: true
|
|
schema:
|
|
type: string
|
|
enum: [system, site, media]
|
|
description: Configuration scope
|
|
example: system
|
|
responses:
|
|
"200":
|
|
description: Config blueprint.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/BlueprintResponse"
|
|
"401":
|
|
$ref: "#/components/responses/Unauthorized"
|
|
"404":
|
|
$ref: "#/components/responses/NotFound"
|
|
|
|
/blueprints/users:
|
|
get:
|
|
operationId: getUserBlueprint
|
|
tags: [Blueprints]
|
|
summary: Get user account blueprint
|
|
description: |
|
|
Returns the blueprint defining the form fields for user account
|
|
editing. Requires `api.users.read` permission.
|
|
responses:
|
|
"200":
|
|
description: User account blueprint.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/BlueprintResponse"
|
|
"401":
|
|
$ref: "#/components/responses/Unauthorized"
|
|
"403":
|
|
$ref: "#/components/responses/Forbidden"
|
|
|
|
/blueprints/users/permissions:
|
|
get:
|
|
operationId: getUserPermissions
|
|
tags: [Blueprints]
|
|
summary: Get all registered permission actions
|
|
description: |
|
|
Returns all registered permission actions as a nested tree structure.
|
|
This is used to render the permissions editor in the admin panel.
|
|
Requires `api.users.read` permission.
|
|
responses:
|
|
"200":
|
|
description: Permission actions tree.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
properties:
|
|
data:
|
|
type: object
|
|
additionalProperties: true
|
|
description: |
|
|
Nested tree of permission actions. Keys are permission
|
|
group names and values are objects with further nesting
|
|
or boolean leaf values.
|
|
example:
|
|
admin:
|
|
login: true
|
|
super: true
|
|
api:
|
|
access: true
|
|
pages:
|
|
read: true
|
|
write: true
|
|
media:
|
|
read: true
|
|
write: true
|
|
users:
|
|
read: true
|
|
write: true
|
|
example:
|
|
data:
|
|
admin:
|
|
login: true
|
|
super: true
|
|
api:
|
|
access: true
|
|
pages:
|
|
read: true
|
|
write: true
|
|
"401":
|
|
$ref: "#/components/responses/Unauthorized"
|
|
"403":
|
|
$ref: "#/components/responses/Forbidden"
|
|
|
|
/blueprints/plugins/{plugin}/pages/{pageId}:
|
|
get:
|
|
operationId: getPluginPageBlueprint
|
|
tags: [Blueprints]
|
|
summary: Get blueprint for a plugin's custom page
|
|
description: |
|
|
Returns the fully resolved blueprint for a plugin's custom admin page.
|
|
This is used to render blueprint-form type pages that plugins register
|
|
in the admin sidebar. Inheritance via `extends@` and `import@`
|
|
directives is resolved server-side.
|
|
parameters:
|
|
- name: plugin
|
|
in: path
|
|
required: true
|
|
schema:
|
|
type: string
|
|
description: Plugin slug (directory name)
|
|
example: api
|
|
- name: pageId
|
|
in: path
|
|
required: true
|
|
schema:
|
|
type: string
|
|
description: Page identifier registered by the plugin
|
|
example: settings
|
|
responses:
|
|
"200":
|
|
description: Plugin page blueprint returned.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/BlueprintResponse"
|
|
"401":
|
|
$ref: "#/components/responses/Unauthorized"
|
|
"404":
|
|
$ref: "#/components/responses/NotFound"
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Data
|
|
# ---------------------------------------------------------------------------
|
|
/data/resolve:
|
|
get:
|
|
operationId: resolveData
|
|
tags: [Data]
|
|
summary: Resolve blueprint data-options@ directives
|
|
description: |
|
|
Resolve a `data-options@` directive from a blueprint field definition.
|
|
The `callable` parameter specifies the PHP callable or data path to
|
|
evaluate. Results are suitable for populating select fields and other
|
|
option-based inputs. Requires `api.pages.read` permission.
|
|
parameters:
|
|
- name: callable
|
|
in: query
|
|
required: true
|
|
description: |
|
|
The callable string from a blueprint `data-options@` directive
|
|
(e.g. `\Grav\Common\Page\Pages::types`).
|
|
schema:
|
|
type: string
|
|
example: '\Grav\Common\Page\Pages::types'
|
|
responses:
|
|
"200":
|
|
description: Resolved data returned.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
properties:
|
|
data:
|
|
description: |
|
|
The resolved data. Shape depends on the callable -- typically
|
|
a key-value object or array suitable for option lists.
|
|
oneOf:
|
|
- type: object
|
|
additionalProperties: true
|
|
- type: array
|
|
items: {}
|
|
example:
|
|
data:
|
|
default: Default
|
|
blog: Blog
|
|
item: Item
|
|
"401":
|
|
$ref: "#/components/responses/Unauthorized"
|
|
"403":
|
|
$ref: "#/components/responses/Forbidden"
|
|
"422":
|
|
$ref: "#/components/responses/ValidationError"
|
|
"429":
|
|
$ref: "#/components/responses/TooManyRequests"
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Thumbnails
|
|
# ---------------------------------------------------------------------------
|
|
/thumbnails/{file}:
|
|
get:
|
|
operationId: getThumbnail
|
|
tags: [Thumbnails]
|
|
summary: Serve cached thumbnail
|
|
description: |
|
|
Serve a cached thumbnail image. This endpoint is public and does not
|
|
require authentication. The `file` parameter is the hashed filename
|
|
generated by the thumbnail cache system.
|
|
security: []
|
|
parameters:
|
|
- name: file
|
|
in: path
|
|
required: true
|
|
description: Cached thumbnail filename (e.g. a hashed filename with extension).
|
|
schema:
|
|
type: string
|
|
example: a1b2c3d4e5f6.jpg
|
|
responses:
|
|
"200":
|
|
description: Thumbnail image binary.
|
|
content:
|
|
image/jpeg:
|
|
schema:
|
|
type: string
|
|
format: binary
|
|
image/png:
|
|
schema:
|
|
type: string
|
|
format: binary
|
|
image/gif:
|
|
schema:
|
|
type: string
|
|
format: binary
|
|
image/webp:
|
|
schema:
|
|
type: string
|
|
format: binary
|
|
"404":
|
|
$ref: "#/components/responses/NotFound"
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Translations
|
|
# ---------------------------------------------------------------------------
|
|
/translations/{lang}:
|
|
get:
|
|
operationId: getTranslations
|
|
tags: [System]
|
|
summary: Get translation strings for a language
|
|
description: |
|
|
Returns all translation strings for the specified language as a flat
|
|
key-value object. Supports optional prefix filtering for partial loads.
|
|
This endpoint does not require authentication.
|
|
security: []
|
|
parameters:
|
|
- name: lang
|
|
in: path
|
|
required: true
|
|
schema:
|
|
type: string
|
|
description: Language code (e.g., `en`, `fr`, `de`)
|
|
example: en
|
|
- name: prefix
|
|
in: query
|
|
required: false
|
|
schema:
|
|
type: string
|
|
description: Filter strings by key prefix (e.g., `PLUGIN_ADMIN`)
|
|
example: PLUGIN_ADMIN
|
|
responses:
|
|
"200":
|
|
description: Translation strings.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
properties:
|
|
data:
|
|
type: object
|
|
properties:
|
|
lang:
|
|
type: string
|
|
example: en
|
|
count:
|
|
type: integer
|
|
example: 1868
|
|
checksum:
|
|
type: string
|
|
description: MD5 hash for cache invalidation.
|
|
example: ca482ce9db905019b7f2be290f018553
|
|
strings:
|
|
type: object
|
|
additionalProperties:
|
|
type: string
|
|
description: Flat key-value map of all translation strings.
|
|
example:
|
|
PLUGIN_ADMIN.TITLE: Title
|
|
PLUGIN_ADMIN.CONTENT: Content
|
|
|
|
# =============================================================================
|
|
# Components
|
|
# =============================================================================
|
|
components:
|
|
# ---------------------------------------------------------------------------
|
|
# Security Schemes
|
|
# ---------------------------------------------------------------------------
|
|
securitySchemes:
|
|
apiToken:
|
|
type: apiKey
|
|
in: header
|
|
name: X-API-Token
|
|
description: |
|
|
Recommended transport for JWT access tokens obtained from `POST /auth/token`.
|
|
Uses a custom header so the token survives FastCGI / PHP-FPM / CGI environments
|
|
(MAMP, shared hosts) that strip the standard `Authorization` header before it
|
|
reaches PHP. Accepts either a bare JWT or the `Bearer <jwt>` form.
|
|
|
|
bearerAuth:
|
|
type: http
|
|
scheme: bearer
|
|
bearerFormat: JWT
|
|
description: |
|
|
Alternative transport for JWT access tokens — standard HTTP Bearer semantics.
|
|
Works on most hosts but may be stripped by some FastCGI / CGI setups; prefer
|
|
`X-API-Token` when portability across hosts matters.
|
|
|
|
apiKey:
|
|
type: apiKey
|
|
in: header
|
|
name: X-API-Key
|
|
description: API key generated via `POST /users/{username}/api-keys`.
|
|
|
|
sessionAuth:
|
|
type: apiKey
|
|
in: cookie
|
|
name: grav-site
|
|
description: Session cookie set by the Grav admin panel login.
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Reusable Parameters
|
|
# ---------------------------------------------------------------------------
|
|
parameters:
|
|
page:
|
|
name: page
|
|
in: query
|
|
description: Page number for pagination (1-based).
|
|
schema:
|
|
type: integer
|
|
minimum: 1
|
|
default: 1
|
|
|
|
per_page:
|
|
name: per_page
|
|
in: query
|
|
description: Number of items per page. Capped at the server's configured maximum (default 100).
|
|
schema:
|
|
type: integer
|
|
minimum: 1
|
|
maximum: 100
|
|
default: 20
|
|
|
|
pageRoute:
|
|
name: route
|
|
in: path
|
|
required: true
|
|
description: |
|
|
The page route without leading slash (e.g. `blog/my-post`). Supports
|
|
nested routes with `/` separators.
|
|
schema:
|
|
type: string
|
|
example: blog/my-first-post
|
|
|
|
configScope:
|
|
name: scope
|
|
in: path
|
|
required: true
|
|
description: |
|
|
Configuration scope. One of: `system`, `site`, `plugins/{name}`,
|
|
`themes/{name}`.
|
|
schema:
|
|
type: string
|
|
examples:
|
|
system:
|
|
value: system
|
|
summary: System configuration
|
|
site:
|
|
value: site
|
|
summary: Site configuration
|
|
plugin:
|
|
value: plugins/api
|
|
summary: Plugin configuration
|
|
theme:
|
|
value: themes/quark
|
|
summary: Theme configuration
|
|
|
|
username:
|
|
name: username
|
|
in: path
|
|
required: true
|
|
description: The username of the user account.
|
|
schema:
|
|
type: string
|
|
pattern: "^[a-zA-Z0-9_-]{3,64}$"
|
|
example: admin
|
|
|
|
ifMatch:
|
|
name: If-Match
|
|
in: header
|
|
required: false
|
|
description: |
|
|
ETag value from the most recent GET response. When provided, the
|
|
server returns `409 Conflict` if the resource has changed since
|
|
the ETag was generated.
|
|
schema:
|
|
type: string
|
|
example: '"a3f8b2c1d4e5f6a7b8c9d0e1f2a3b4c5"'
|
|
|
|
lang:
|
|
name: lang
|
|
in: query
|
|
required: false
|
|
description: |
|
|
Two-letter language code. When provided, the request operates on
|
|
the page translation for that language. If omitted, the default
|
|
site language is used.
|
|
schema:
|
|
type: string
|
|
example: en
|
|
|
|
webhookId:
|
|
name: id
|
|
in: path
|
|
required: true
|
|
description: Unique identifier of the webhook.
|
|
schema:
|
|
type: string
|
|
example: wh_abc123
|
|
|
|
gravEnvironment:
|
|
name: X-Grav-Environment
|
|
in: header
|
|
required: false
|
|
description: |
|
|
Target Grav environment. Defaults to the auto-detected environment
|
|
(from hostname) if omitted. Use `GET /system/environments` to
|
|
discover available environments.
|
|
schema:
|
|
type: string
|
|
example: localhost
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Reusable Headers
|
|
# ---------------------------------------------------------------------------
|
|
headers:
|
|
ETag:
|
|
description: Entity tag for optimistic concurrency control.
|
|
schema:
|
|
type: string
|
|
example: '"a3f8b2c1d4e5f6a7b8c9d0e1f2a3b4c5"'
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Reusable Responses
|
|
# ---------------------------------------------------------------------------
|
|
responses:
|
|
Unauthorized:
|
|
description: Authentication is required or the provided credentials are invalid.
|
|
content:
|
|
application/problem+json:
|
|
schema:
|
|
$ref: "#/components/schemas/ErrorResponse"
|
|
example:
|
|
status: 401
|
|
title: Unauthorized
|
|
detail: Invalid username or password.
|
|
|
|
Forbidden:
|
|
description: The authenticated user does not have the required permission.
|
|
content:
|
|
application/problem+json:
|
|
schema:
|
|
$ref: "#/components/schemas/ErrorResponse"
|
|
example:
|
|
status: 403
|
|
title: Forbidden
|
|
detail: "Missing required permission: api.pages.write"
|
|
|
|
NotFound:
|
|
description: The requested resource was not found.
|
|
content:
|
|
application/problem+json:
|
|
schema:
|
|
$ref: "#/components/schemas/ErrorResponse"
|
|
example:
|
|
status: 404
|
|
title: Not Found
|
|
detail: "Page not found at route: /nonexistent"
|
|
|
|
Conflict:
|
|
description: |
|
|
The resource has been modified since the provided ETag was
|
|
generated. Fetch the latest version and retry.
|
|
content:
|
|
application/problem+json:
|
|
schema:
|
|
$ref: "#/components/schemas/ErrorResponse"
|
|
example:
|
|
status: 409
|
|
title: Conflict
|
|
detail: The resource has been modified since you last retrieved it. Please fetch the latest version and try again.
|
|
|
|
ValidationError:
|
|
description: The request body failed validation.
|
|
content:
|
|
application/problem+json:
|
|
schema:
|
|
allOf:
|
|
- $ref: "#/components/schemas/ErrorResponse"
|
|
- type: object
|
|
properties:
|
|
errors:
|
|
type: array
|
|
items:
|
|
$ref: "#/components/schemas/ValidationError"
|
|
example:
|
|
status: 422
|
|
title: Unprocessable Entity
|
|
detail: "Missing required fields: title, route"
|
|
errors:
|
|
- field: title
|
|
message: "The 'title' field is required."
|
|
- field: route
|
|
message: "The 'route' field is required."
|
|
|
|
TooManyRequests:
|
|
description: Rate limit exceeded.
|
|
headers:
|
|
X-RateLimit-Limit:
|
|
description: Maximum requests allowed per window.
|
|
schema:
|
|
type: integer
|
|
X-RateLimit-Remaining:
|
|
description: Requests remaining in the current window.
|
|
schema:
|
|
type: integer
|
|
X-RateLimit-Reset:
|
|
description: UTC epoch timestamp when the rate limit window resets.
|
|
schema:
|
|
type: integer
|
|
content:
|
|
application/problem+json:
|
|
schema:
|
|
$ref: "#/components/schemas/ErrorResponse"
|
|
example:
|
|
status: 429
|
|
title: Too Many Requests
|
|
detail: Rate limit exceeded. Try again later.
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Schemas
|
|
# ---------------------------------------------------------------------------
|
|
schemas:
|
|
# -- Blueprint -------------------------------------------------------------
|
|
BlueprintResponse:
|
|
type: object
|
|
properties:
|
|
data:
|
|
$ref: "#/components/schemas/Blueprint"
|
|
|
|
Blueprint:
|
|
type: object
|
|
description: A resolved blueprint schema defining form fields for content or configuration editing.
|
|
properties:
|
|
name:
|
|
type: string
|
|
description: Blueprint identifier (template name, plugin slug, etc.)
|
|
example: blog
|
|
title:
|
|
type: string
|
|
description: Human-readable title.
|
|
example: Blog
|
|
type:
|
|
type: string
|
|
nullable: true
|
|
description: Blueprint type (plugin, theme, etc.)
|
|
child_type:
|
|
type: string
|
|
nullable: true
|
|
description: Default child page type for this template.
|
|
example: item
|
|
validation:
|
|
type: string
|
|
description: Validation mode (`strict` or `loose`).
|
|
example: loose
|
|
fields:
|
|
type: array
|
|
description: Top-level field definitions (typically a `tabs` container).
|
|
items:
|
|
$ref: "#/components/schemas/BlueprintField"
|
|
|
|
BlueprintField:
|
|
type: object
|
|
description: A single form field definition.
|
|
required: [name, type]
|
|
properties:
|
|
name:
|
|
type: string
|
|
description: Dot-notation field path (e.g. `header.content.limit`).
|
|
example: header.title
|
|
type:
|
|
type: string
|
|
description: |
|
|
Field type. Standard types include: `text`, `textarea`, `select`, `toggle`,
|
|
`checkbox`, `radio`, `markdown`, `editor`, `filepicker`, `pagemedia`,
|
|
`taxonomy`, `list`, `array`, `tabs`, `tab`, `section`, `fieldset`,
|
|
`columns`, `column`, `spacer`, `display`, `hidden`.
|
|
example: text
|
|
label:
|
|
type: string
|
|
description: Display label (may contain translation keys like `PLUGIN_ADMIN.TITLE`).
|
|
help:
|
|
type: string
|
|
description: Help text shown below the field.
|
|
placeholder:
|
|
type: string
|
|
default:
|
|
description: Default value for the field.
|
|
description:
|
|
type: string
|
|
description: Extended description (may contain HTML if `markdown` is true).
|
|
title:
|
|
type: string
|
|
description: Section or spacer title.
|
|
options:
|
|
type: object
|
|
additionalProperties:
|
|
type: string
|
|
description: Key-value options for select, radio, toggle fields.
|
|
example: { "asc": "Ascending", "desc": "Descending" }
|
|
validate:
|
|
type: object
|
|
description: Validation rules.
|
|
properties:
|
|
type:
|
|
type: string
|
|
example: int
|
|
required:
|
|
type: boolean
|
|
pattern:
|
|
type: string
|
|
min:
|
|
type: number
|
|
max:
|
|
type: number
|
|
fields:
|
|
type: array
|
|
description: Nested child fields (for tabs, tab, section, fieldset, columns, list).
|
|
items:
|
|
$ref: "#/components/schemas/BlueprintField"
|
|
toggleable:
|
|
type: boolean
|
|
multiple:
|
|
type: boolean
|
|
markdown:
|
|
type: boolean
|
|
description: If true, the description contains HTML/markdown.
|
|
underline:
|
|
type: boolean
|
|
size:
|
|
type: string
|
|
enum: [small, medium, large]
|
|
rows:
|
|
type: integer
|
|
description: Number of rows for textarea fields.
|
|
|
|
# -- Sidebar --------------------------------------------------------------
|
|
SidebarItem:
|
|
type: object
|
|
description: A sidebar navigation item contributed by a plugin.
|
|
properties:
|
|
id:
|
|
type: string
|
|
description: Unique identifier for the sidebar item.
|
|
example: dashboard
|
|
plugin:
|
|
type: string
|
|
description: Plugin slug that registered this item.
|
|
example: admin-next
|
|
label:
|
|
type: string
|
|
description: Display label for the sidebar item.
|
|
example: Dashboard
|
|
icon:
|
|
type: string
|
|
description: Icon identifier (e.g. Lucide icon name).
|
|
example: gauge
|
|
route:
|
|
type: string
|
|
description: Admin route this item navigates to.
|
|
example: /dashboard
|
|
priority:
|
|
type: integer
|
|
description: Sort priority (higher values appear first).
|
|
example: 100
|
|
badge:
|
|
type: string
|
|
nullable: true
|
|
description: Optional badge text (e.g. a count or status indicator).
|
|
example: null
|
|
|
|
# -- Plugin Page Definition ------------------------------------------------
|
|
PluginPageDefinition:
|
|
type: object
|
|
description: Admin page definition registered by a plugin.
|
|
properties:
|
|
id:
|
|
type: string
|
|
description: Unique page identifier.
|
|
example: api-settings
|
|
plugin:
|
|
type: string
|
|
description: Plugin slug that registered this page.
|
|
example: api
|
|
title:
|
|
type: string
|
|
description: Page title.
|
|
example: API Settings
|
|
icon:
|
|
type: string
|
|
description: Icon identifier for the page.
|
|
example: plug
|
|
page_type:
|
|
type: string
|
|
description: |
|
|
Page rendering type. `blueprint-form` renders a standard blueprint
|
|
form; `custom` uses a plugin-provided web component.
|
|
example: blueprint-form
|
|
blueprint:
|
|
type: string
|
|
nullable: true
|
|
description: API path to the blueprint for this page (if page_type is blueprint-form).
|
|
example: /blueprints/plugins/api/pages/settings
|
|
data_endpoint:
|
|
type: string
|
|
nullable: true
|
|
description: API path to fetch current data for the form.
|
|
example: /config/plugins/api
|
|
save_endpoint:
|
|
type: string
|
|
nullable: true
|
|
description: API path to save form data.
|
|
example: /config/plugins/api
|
|
actions:
|
|
type: array
|
|
items:
|
|
type: string
|
|
description: Available actions for the page toolbar.
|
|
example: [save]
|
|
has_custom_component:
|
|
type: boolean
|
|
description: Whether the plugin provides a custom web component for this page.
|
|
example: false
|
|
|
|
# -- Page -----------------------------------------------------------------
|
|
Page:
|
|
type: object
|
|
description: Full page object including content, media, and optional children.
|
|
properties:
|
|
route:
|
|
type: string
|
|
description: The page route (e.g. `/blog/my-post`).
|
|
example: /blog/my-first-post
|
|
slug:
|
|
type: string
|
|
description: URL-safe slug derived from the directory name.
|
|
example: my-first-post
|
|
title:
|
|
type: string
|
|
description: The page title from the header.
|
|
example: My First Post
|
|
template:
|
|
type: string
|
|
description: The Twig template used to render this page.
|
|
example: blog
|
|
header:
|
|
type: object
|
|
additionalProperties: true
|
|
description: The full frontmatter header as a key-value object.
|
|
example:
|
|
title: My First Post
|
|
taxonomy:
|
|
category: [blog]
|
|
tag: [intro, grav]
|
|
taxonomy:
|
|
type: object
|
|
additionalProperties:
|
|
type: array
|
|
items:
|
|
type: string
|
|
description: Taxonomy types and their values for this page.
|
|
example:
|
|
category: [blog]
|
|
tag: [intro, grav]
|
|
published:
|
|
type: boolean
|
|
description: Whether the page is published.
|
|
example: true
|
|
visible:
|
|
type: boolean
|
|
description: Whether the page is visible in navigation menus.
|
|
example: true
|
|
routable:
|
|
type: boolean
|
|
description: Whether the page is routable (accessible via URL).
|
|
example: true
|
|
date:
|
|
type: string
|
|
format: date-time
|
|
nullable: true
|
|
description: Page date as ISO 8601.
|
|
example: "2026-03-20T12:00:00+00:00"
|
|
modified:
|
|
type: string
|
|
format: date-time
|
|
nullable: true
|
|
description: Last modification time as ISO 8601.
|
|
example: "2026-03-22T09:30:00+00:00"
|
|
order:
|
|
type: integer
|
|
nullable: true
|
|
description: Numeric ordering prefix, or `null` for unordered pages.
|
|
example: 1
|
|
has_children:
|
|
type: boolean
|
|
description: Whether this page has child pages. Useful for tree/column UIs to show expand indicators without loading children.
|
|
example: true
|
|
content:
|
|
type: string
|
|
description: Raw Markdown content of the page body.
|
|
example: |
|
|
# Hello World
|
|
|
|
This is the body of the post.
|
|
content_html:
|
|
type: string
|
|
description: |
|
|
Rendered HTML content. Only present when `render=true` is
|
|
requested.
|
|
example: "<h1>Hello World</h1>\n<p>This is the body of the post.</p>"
|
|
media:
|
|
type: array
|
|
description: Media files attached to this page.
|
|
items:
|
|
type: object
|
|
properties:
|
|
filename:
|
|
type: string
|
|
example: hero.jpg
|
|
type:
|
|
type: string
|
|
description: MIME type of the file.
|
|
example: image/jpeg
|
|
size:
|
|
type: integer
|
|
description: File size in bytes.
|
|
example: 204800
|
|
children:
|
|
type: array
|
|
description: |
|
|
Child pages. Only present when `children=true` is requested.
|
|
Each child is a full Page object (recursion depth controlled by
|
|
`children_depth`).
|
|
items:
|
|
$ref: "#/components/schemas/Page"
|
|
language:
|
|
type: string
|
|
description: Language code of the page content (e.g. `en`, `fr`).
|
|
example: en
|
|
translated_languages:
|
|
type: array
|
|
description: |
|
|
Languages this page has been translated into. Only present when
|
|
`translations=true` is requested.
|
|
items:
|
|
type: string
|
|
example: [en, fr]
|
|
untranslated_languages:
|
|
type: array
|
|
description: |
|
|
Configured languages that this page has not been translated into.
|
|
Only present when `translations=true` is requested.
|
|
items:
|
|
type: string
|
|
example: [de, es]
|
|
required:
|
|
- route
|
|
- slug
|
|
- title
|
|
- template
|
|
- header
|
|
- taxonomy
|
|
- published
|
|
- visible
|
|
- routable
|
|
- date
|
|
- modified
|
|
- order
|
|
|
|
PageSummary:
|
|
type: object
|
|
description: |
|
|
Lightweight page representation used in list responses. Excludes
|
|
content, media, and children for performance.
|
|
properties:
|
|
route:
|
|
type: string
|
|
example: /blog/my-first-post
|
|
slug:
|
|
type: string
|
|
example: my-first-post
|
|
title:
|
|
type: string
|
|
example: My First Post
|
|
template:
|
|
type: string
|
|
example: blog
|
|
header:
|
|
type: object
|
|
additionalProperties: true
|
|
taxonomy:
|
|
type: object
|
|
additionalProperties:
|
|
type: array
|
|
items:
|
|
type: string
|
|
published:
|
|
type: boolean
|
|
example: true
|
|
visible:
|
|
type: boolean
|
|
example: true
|
|
routable:
|
|
type: boolean
|
|
example: true
|
|
date:
|
|
type: string
|
|
format: date-time
|
|
nullable: true
|
|
example: "2026-03-20T12:00:00+00:00"
|
|
modified:
|
|
type: string
|
|
format: date-time
|
|
nullable: true
|
|
example: "2026-03-22T09:30:00+00:00"
|
|
order:
|
|
type: integer
|
|
nullable: true
|
|
example: 1
|
|
language:
|
|
type: string
|
|
description: Language code of the page content (e.g. `en`, `fr`).
|
|
example: en
|
|
required:
|
|
- route
|
|
- slug
|
|
- title
|
|
- template
|
|
- header
|
|
- taxonomy
|
|
- published
|
|
- visible
|
|
- routable
|
|
- date
|
|
- modified
|
|
- order
|
|
|
|
# -- Media ----------------------------------------------------------------
|
|
MediaItem:
|
|
type: object
|
|
description: A media file, either from a page or the site-level media folder.
|
|
properties:
|
|
filename:
|
|
type: string
|
|
description: The file name.
|
|
example: hero-image.jpg
|
|
path:
|
|
type: string
|
|
description: Relative path from the media root, including the filename.
|
|
example: blog/2026/hero-image.jpg
|
|
url:
|
|
type: string
|
|
description: Relative URL path to the file.
|
|
example: /user/images/hero-image.jpg
|
|
type:
|
|
type: string
|
|
description: MIME type of the file.
|
|
example: image/jpeg
|
|
size:
|
|
type: integer
|
|
description: File size in bytes.
|
|
example: 204800
|
|
dimensions:
|
|
type: object
|
|
nullable: true
|
|
description: Image dimensions. Only present for image files.
|
|
properties:
|
|
width:
|
|
type: integer
|
|
example: 1920
|
|
height:
|
|
type: integer
|
|
example: 1080
|
|
modified:
|
|
type: string
|
|
format: date-time
|
|
description: Last modification time as ISO 8601.
|
|
example: "2026-03-22T09:30:00+00:00"
|
|
required:
|
|
- filename
|
|
- url
|
|
- type
|
|
- size
|
|
- modified
|
|
|
|
FolderInfo:
|
|
type: object
|
|
properties:
|
|
name:
|
|
type: string
|
|
description: Folder name.
|
|
example: 2026
|
|
path:
|
|
type: string
|
|
description: Full relative path from the media root.
|
|
example: blog/2026
|
|
children_count:
|
|
type: integer
|
|
description: Number of immediate subdirectories.
|
|
example: 1
|
|
file_count:
|
|
type: integer
|
|
description: Number of files in this folder (excluding metadata files).
|
|
example: 5
|
|
|
|
# -- User -----------------------------------------------------------------
|
|
User:
|
|
type: object
|
|
description: A Grav user account.
|
|
properties:
|
|
username:
|
|
type: string
|
|
example: admin
|
|
email:
|
|
type: string
|
|
format: email
|
|
nullable: true
|
|
example: admin@example.com
|
|
fullname:
|
|
type: string
|
|
nullable: true
|
|
example: Site Administrator
|
|
title:
|
|
type: string
|
|
nullable: true
|
|
description: Job title or role label.
|
|
example: Administrator
|
|
state:
|
|
type: string
|
|
enum: [enabled, disabled]
|
|
description: Whether the account is active.
|
|
example: enabled
|
|
avatar_url:
|
|
type: string
|
|
nullable: true
|
|
description: URL to the user's avatar image, or `null` if no avatar is set.
|
|
example: /user/accounts/admin/avatar.png
|
|
twofa_enabled:
|
|
type: boolean
|
|
description: Whether two-factor authentication is currently enabled for this user.
|
|
example: false
|
|
twofa_secret:
|
|
type: boolean
|
|
description: Whether a 2FA secret exists for this user (does not expose the actual secret).
|
|
example: false
|
|
access:
|
|
type: object
|
|
additionalProperties: true
|
|
description: |
|
|
Permission tree. Keys are dot-separated permission groups, values
|
|
are booleans or nested objects.
|
|
example:
|
|
admin:
|
|
super: true
|
|
api:
|
|
access: true
|
|
created:
|
|
type: string
|
|
format: date-time
|
|
nullable: true
|
|
example: "2026-01-15T10:00:00+00:00"
|
|
modified:
|
|
type: string
|
|
format: date-time
|
|
nullable: true
|
|
example: "2026-03-22T09:30:00+00:00"
|
|
required:
|
|
- username
|
|
- state
|
|
- access
|
|
|
|
# -- Config ---------------------------------------------------------------
|
|
ConfigObject:
|
|
type: object
|
|
additionalProperties: true
|
|
description: |
|
|
An arbitrary configuration object. The shape depends on the scope.
|
|
For scalar values, the response wraps the value as `{"value": ...}`.
|
|
example:
|
|
pages:
|
|
theme: quark
|
|
debugger:
|
|
enabled: false
|
|
|
|
# -- System ---------------------------------------------------------------
|
|
SystemInfo:
|
|
type: object
|
|
description: Runtime information about the Grav installation.
|
|
properties:
|
|
grav_version:
|
|
type: string
|
|
description: The installed Grav core version.
|
|
example: 1.7.48
|
|
php_version:
|
|
type: string
|
|
description: PHP runtime version.
|
|
example: 8.3.14
|
|
php_extensions:
|
|
type: array
|
|
items:
|
|
type: string
|
|
description: List of loaded PHP extensions.
|
|
example: [curl, gd, intl, json, mbstring, openssl, pdo, zip]
|
|
server_software:
|
|
type: string
|
|
description: Web server identification string.
|
|
example: Apache/2.4.58 (Unix)
|
|
environment:
|
|
type: string
|
|
description: The active Grav environment name.
|
|
example: localhost
|
|
plugins:
|
|
type: array
|
|
items:
|
|
type: object
|
|
properties:
|
|
name:
|
|
type: string
|
|
example: API
|
|
version:
|
|
type: string
|
|
example: 1.0.0-beta.1
|
|
enabled:
|
|
type: boolean
|
|
example: true
|
|
description: Installed plugins with version and enabled state.
|
|
themes:
|
|
type: array
|
|
items:
|
|
type: object
|
|
properties:
|
|
name:
|
|
type: string
|
|
example: Quark
|
|
version:
|
|
type: string
|
|
example: 2.0.4
|
|
active:
|
|
type: boolean
|
|
example: true
|
|
description: Installed themes with version and active state.
|
|
required:
|
|
- grav_version
|
|
- php_version
|
|
- php_extensions
|
|
- server_software
|
|
- environment
|
|
- plugins
|
|
- themes
|
|
|
|
LogEntry:
|
|
type: object
|
|
description: A single log file entry from `grav.log`.
|
|
properties:
|
|
date:
|
|
type: string
|
|
nullable: true
|
|
description: Timestamp of the log entry.
|
|
example: "2026-03-22 09:30:15"
|
|
level:
|
|
type: string
|
|
description: Log level.
|
|
enum: [DEBUG, INFO, NOTICE, WARNING, ERROR, CRITICAL, ALERT, EMERGENCY]
|
|
example: ERROR
|
|
message:
|
|
type: string
|
|
description: The log message.
|
|
example: "Class 'Grav\\Plugin\\FooPlugin' not found"
|
|
context:
|
|
type: string
|
|
description: Additional context or stack trace information.
|
|
example: ""
|
|
required:
|
|
- date
|
|
- level
|
|
- message
|
|
|
|
Backup:
|
|
type: object
|
|
description: Metadata about a backup archive.
|
|
properties:
|
|
filename:
|
|
type: string
|
|
description: Name of the backup archive file.
|
|
example: grav-backup-2026-03-22T09-30-00.zip
|
|
path:
|
|
type: string
|
|
description: Absolute filesystem path. Only present on creation responses.
|
|
example: /var/www/grav/backup/grav-backup-2026-03-22T09-30-00.zip
|
|
size:
|
|
type: integer
|
|
description: Archive file size in bytes.
|
|
example: 52428800
|
|
date:
|
|
type: string
|
|
format: date-time
|
|
nullable: true
|
|
description: Creation timestamp as ISO 8601.
|
|
example: "2026-03-22T09:30:00+00:00"
|
|
required:
|
|
- filename
|
|
- size
|
|
|
|
ApiKey:
|
|
type: object
|
|
description: |
|
|
API key metadata. The key hash is never exposed; only the prefix
|
|
(first 12 characters plus `...`) is shown.
|
|
properties:
|
|
id:
|
|
type: string
|
|
description: Unique identifier for the key.
|
|
example: a1b2c3d4e5f6a7b8
|
|
name:
|
|
type: string
|
|
description: Human-readable label for the key.
|
|
example: CI/CD Pipeline
|
|
prefix:
|
|
type: string
|
|
description: First 12 characters of the key plus `...` for identification.
|
|
example: grav_a1b2c3d4...
|
|
scopes:
|
|
type: array
|
|
items:
|
|
type: string
|
|
description: Permission scopes this key is restricted to. Empty means unrestricted.
|
|
example: [api.pages.read, api.media.read]
|
|
active:
|
|
type: boolean
|
|
description: Whether the key is currently active.
|
|
example: true
|
|
created:
|
|
type: integer
|
|
nullable: true
|
|
description: Unix timestamp when the key was created.
|
|
example: 1711094400
|
|
last_used:
|
|
type: integer
|
|
nullable: true
|
|
description: Unix timestamp when the key was last used, or `null` if never used.
|
|
example: 1711180800
|
|
expires:
|
|
type: integer
|
|
nullable: true
|
|
description: Unix timestamp when the key expires, or `null` for no expiry.
|
|
example: null
|
|
required:
|
|
- id
|
|
- name
|
|
- prefix
|
|
- scopes
|
|
- active
|
|
|
|
# -- Auth -----------------------------------------------------------------
|
|
TokenResponse:
|
|
type: object
|
|
description: JWT token pair returned by the auth endpoints.
|
|
properties:
|
|
access_token:
|
|
type: string
|
|
description: Short-lived JWT access token.
|
|
example: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
|
|
refresh_token:
|
|
type: string
|
|
description: Long-lived refresh token for obtaining new access tokens.
|
|
example: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
|
|
token_type:
|
|
type: string
|
|
description: Token type. Always `Bearer`.
|
|
example: Bearer
|
|
enum: [Bearer]
|
|
expires_in:
|
|
type: integer
|
|
description: Access token lifetime in seconds.
|
|
example: 3600
|
|
required:
|
|
- access_token
|
|
- refresh_token
|
|
- token_type
|
|
- expires_in
|
|
|
|
# -- Pagination Envelope --------------------------------------------------
|
|
PaginatedEnvelope:
|
|
type: object
|
|
description: Standard paginated response wrapper.
|
|
properties:
|
|
data:
|
|
type: array
|
|
items: {}
|
|
meta:
|
|
type: object
|
|
properties:
|
|
pagination:
|
|
type: object
|
|
properties:
|
|
page:
|
|
type: integer
|
|
description: Current page number (1-based).
|
|
example: 1
|
|
per_page:
|
|
type: integer
|
|
description: Items per page.
|
|
example: 20
|
|
total:
|
|
type: integer
|
|
description: Total number of items across all pages.
|
|
example: 42
|
|
total_pages:
|
|
type: integer
|
|
description: Total number of pages.
|
|
example: 3
|
|
required: [page, per_page, total, total_pages]
|
|
required: [pagination]
|
|
links:
|
|
type: object
|
|
properties:
|
|
self:
|
|
type: string
|
|
description: URL of the current page.
|
|
example: "/api/v1/pages?page=1&per_page=20"
|
|
first:
|
|
type: string
|
|
description: URL of the first page. Present when `page > 1`.
|
|
example: "/api/v1/pages?page=1&per_page=20"
|
|
prev:
|
|
type: string
|
|
description: URL of the previous page. Present when `page > 1`.
|
|
example: "/api/v1/pages?page=1&per_page=20"
|
|
next:
|
|
type: string
|
|
description: URL of the next page. Present when more pages exist.
|
|
example: "/api/v1/pages?page=2&per_page=20"
|
|
last:
|
|
type: string
|
|
description: URL of the last page. Present when more pages exist.
|
|
example: "/api/v1/pages?page=3&per_page=20"
|
|
required: [self]
|
|
required: [data, meta, links]
|
|
|
|
# -- Error ----------------------------------------------------------------
|
|
ErrorResponse:
|
|
type: object
|
|
description: |
|
|
Error response following RFC 7807 Problem Details. Returned with
|
|
content type `application/problem+json`.
|
|
properties:
|
|
status:
|
|
type: integer
|
|
description: HTTP status code.
|
|
example: 422
|
|
title:
|
|
type: string
|
|
description: Short, human-readable summary of the problem type.
|
|
example: Unprocessable Entity
|
|
detail:
|
|
type: string
|
|
description: Human-readable explanation specific to this occurrence.
|
|
example: "Missing required fields: title"
|
|
errors:
|
|
type: array
|
|
description: |
|
|
Field-level validation errors. Only present on 422 responses
|
|
when individual field errors are available.
|
|
items:
|
|
$ref: "#/components/schemas/ValidationError"
|
|
required:
|
|
- status
|
|
- title
|
|
- detail
|
|
|
|
ValidationError:
|
|
type: object
|
|
description: A single field-level validation error.
|
|
properties:
|
|
field:
|
|
type: string
|
|
description: The name of the field that failed validation.
|
|
example: title
|
|
message:
|
|
type: string
|
|
description: Human-readable description of the validation failure.
|
|
example: "The 'title' field is required."
|
|
required:
|
|
- field
|
|
- message
|
|
|
|
# -- Request Bodies -------------------------------------------------------
|
|
CreatePageRequest:
|
|
type: object
|
|
description: Request body for creating a new page.
|
|
required: [route, title]
|
|
properties:
|
|
route:
|
|
type: string
|
|
description: Full route for the new page (e.g. `/blog/my-new-post`).
|
|
example: /blog/my-new-post
|
|
title:
|
|
type: string
|
|
description: Page title.
|
|
example: My New Post
|
|
template:
|
|
type: string
|
|
description: Template name. Defaults to `default` if omitted.
|
|
default: default
|
|
example: blog
|
|
content:
|
|
type: string
|
|
description: Raw Markdown body content.
|
|
default: ""
|
|
example: |
|
|
# Hello World
|
|
|
|
This is the body of my new post.
|
|
header:
|
|
type: object
|
|
additionalProperties: true
|
|
description: |
|
|
Additional frontmatter fields to merge into the page header.
|
|
The `title` from the top-level field is always included.
|
|
example:
|
|
taxonomy:
|
|
category: [blog]
|
|
tag: [new]
|
|
order:
|
|
oneOf:
|
|
- type: integer
|
|
- type: string
|
|
enum: [auto]
|
|
nullable: true
|
|
description: |
|
|
Numeric ordering prefix. When set to an integer, creates a visible
|
|
(ordered) page directory (e.g. `03.my-new-post`). When `null`,
|
|
creates an unordered page. Pass the string `"auto"` to mirror the
|
|
admin-classic add-page behavior: the API scans the parent's direct
|
|
children and assigns the next available number when any sibling
|
|
already uses numeric prefixes; otherwise the new page is created
|
|
without a numeric prefix.
|
|
example: 3
|
|
lang:
|
|
type: string
|
|
description: |
|
|
Two-letter language code. When provided, creates the page in the
|
|
specified language. If omitted, uses the default site language.
|
|
example: en
|
|
|
|
UpdatePageRequest:
|
|
type: object
|
|
description: |
|
|
Request body for partially updating a page. All fields are optional;
|
|
only provided fields are changed.
|
|
properties:
|
|
title:
|
|
type: string
|
|
description: New page title.
|
|
example: Updated Post Title
|
|
content:
|
|
type: string
|
|
description: New raw Markdown body content.
|
|
example: |
|
|
# Updated
|
|
|
|
New content here.
|
|
template:
|
|
type: string
|
|
description: Change the page template.
|
|
example: blog
|
|
header:
|
|
type: object
|
|
additionalProperties: true
|
|
description: |
|
|
Header fields to merge into the existing frontmatter. Existing
|
|
fields not included here are preserved.
|
|
example:
|
|
taxonomy:
|
|
tag: [updated]
|
|
published:
|
|
type: boolean
|
|
description: Set the published state.
|
|
example: true
|
|
visible:
|
|
type: boolean
|
|
description: Set the visible state.
|
|
example: true
|
|
|
|
MovePageRequest:
|
|
type: object
|
|
description: Request body for moving a page.
|
|
required: [parent]
|
|
properties:
|
|
parent:
|
|
type: string
|
|
description: Route of the new parent page (use `/` for root).
|
|
example: /tutorials
|
|
slug:
|
|
type: string
|
|
description: New slug. Defaults to the current slug if omitted.
|
|
example: renamed-post
|
|
order:
|
|
type: integer
|
|
nullable: true
|
|
description: New ordering prefix. Defaults to the current order if omitted.
|
|
example: 5
|
|
|
|
CreateUserRequest:
|
|
type: object
|
|
description: Request body for creating a new user account.
|
|
required: [username, password, email]
|
|
properties:
|
|
username:
|
|
type: string
|
|
description: |
|
|
Unique username. Must be 3-64 characters containing only letters,
|
|
numbers, hyphens, and underscores.
|
|
pattern: "^[a-zA-Z0-9_-]{3,64}$"
|
|
example: johndoe
|
|
password:
|
|
type: string
|
|
format: password
|
|
description: Password for the new account.
|
|
example: s3cureP@ss
|
|
email:
|
|
type: string
|
|
format: email
|
|
description: Email address.
|
|
example: john@example.com
|
|
fullname:
|
|
type: string
|
|
description: Full display name.
|
|
default: ""
|
|
example: John Doe
|
|
title:
|
|
type: string
|
|
description: Job title or role label.
|
|
default: ""
|
|
example: Editor
|
|
state:
|
|
type: string
|
|
enum: [enabled, disabled]
|
|
description: Initial account state.
|
|
default: enabled
|
|
example: enabled
|
|
access:
|
|
type: object
|
|
additionalProperties: true
|
|
description: Permission tree for the new user.
|
|
example:
|
|
api:
|
|
access: true
|
|
pages:
|
|
read: true
|
|
write: true
|
|
|
|
UpdateUserRequest:
|
|
type: object
|
|
description: |
|
|
Request body for partially updating a user. All fields are optional;
|
|
only provided fields are changed.
|
|
properties:
|
|
email:
|
|
type: string
|
|
format: email
|
|
example: jane@example.com
|
|
fullname:
|
|
type: string
|
|
example: Jane Doe
|
|
title:
|
|
type: string
|
|
example: Senior Editor
|
|
state:
|
|
type: string
|
|
enum: [enabled, disabled]
|
|
example: enabled
|
|
password:
|
|
type: string
|
|
format: password
|
|
description: New password. Only set if a non-empty value is provided.
|
|
access:
|
|
type: object
|
|
additionalProperties: true
|
|
description: Updated permission tree.
|
|
|
|
# -- Languages ------------------------------------------------------------
|
|
LanguageInfo:
|
|
type: object
|
|
description: Site language configuration.
|
|
properties:
|
|
enabled:
|
|
type: boolean
|
|
description: Whether multi-language support is enabled.
|
|
example: true
|
|
languages:
|
|
type: array
|
|
items:
|
|
type: string
|
|
description: List of configured language codes.
|
|
example: [en, fr, de]
|
|
default:
|
|
type: string
|
|
description: The default language code.
|
|
example: en
|
|
active:
|
|
type: string
|
|
description: The currently active language based on the request context.
|
|
example: en
|
|
required:
|
|
- enabled
|
|
- languages
|
|
- default
|
|
- active
|
|
|
|
TranslationInfo:
|
|
type: object
|
|
description: Translation status for a page across configured languages.
|
|
properties:
|
|
route:
|
|
type: string
|
|
description: The page route.
|
|
example: /blog/my-first-post
|
|
default_language:
|
|
type: string
|
|
description: The default language code.
|
|
example: en
|
|
translated:
|
|
type: object
|
|
additionalProperties:
|
|
type: object
|
|
properties:
|
|
title:
|
|
type: string
|
|
description: Page title in that language.
|
|
last_modified:
|
|
type: string
|
|
format: date-time
|
|
description: Last modification time of the translation.
|
|
description: |
|
|
Map of language code to translation metadata for each language
|
|
that has a translation.
|
|
example:
|
|
en:
|
|
title: My First Post
|
|
last_modified: "2026-03-22T09:30:00+00:00"
|
|
fr:
|
|
title: Mon Premier Article
|
|
last_modified: "2026-03-23T11:00:00+00:00"
|
|
untranslated:
|
|
type: array
|
|
items:
|
|
type: string
|
|
description: Language codes that do not yet have a translation.
|
|
example: [de, es]
|
|
required:
|
|
- route
|
|
- default_language
|
|
- translated
|
|
- untranslated
|
|
|
|
# -- GPM ------------------------------------------------------------------
|
|
Package:
|
|
type: object
|
|
description: A Grav plugin or theme package.
|
|
properties:
|
|
slug:
|
|
type: string
|
|
description: Package slug identifier.
|
|
example: admin
|
|
name:
|
|
type: string
|
|
description: Human-readable package name.
|
|
example: Admin Panel
|
|
version:
|
|
type: string
|
|
description: Installed version, or latest available version for repository packages.
|
|
example: 1.10.44
|
|
type:
|
|
type: string
|
|
enum: [plugin, theme]
|
|
description: Package type.
|
|
example: plugin
|
|
enabled:
|
|
type: boolean
|
|
description: Whether the package is currently enabled.
|
|
example: true
|
|
update_available:
|
|
type: boolean
|
|
description: Whether a newer version is available in the repository.
|
|
example: false
|
|
available_version:
|
|
type: string
|
|
nullable: true
|
|
description: The latest available version, or `null` if up to date.
|
|
example: null
|
|
author:
|
|
type: string
|
|
description: Package author name.
|
|
example: Team Grav
|
|
description:
|
|
type: string
|
|
description: Short description of the package.
|
|
example: Provides an admin panel for Grav CMS.
|
|
homepage:
|
|
type: string
|
|
nullable: true
|
|
description: URL to the package homepage or repository.
|
|
example: https://github.com/getgrav/grav-plugin-admin
|
|
keywords:
|
|
type: array
|
|
items:
|
|
type: string
|
|
description: Tags or keywords associated with the package.
|
|
example: [admin, panel, manager]
|
|
installed:
|
|
type: boolean
|
|
description: Whether the package is installed locally. Used in repository listings.
|
|
example: true
|
|
required:
|
|
- slug
|
|
- name
|
|
- version
|
|
- type
|
|
|
|
# -- Scheduler ------------------------------------------------------------
|
|
Job:
|
|
type: object
|
|
description: A configured scheduler job.
|
|
properties:
|
|
id:
|
|
type: string
|
|
description: Unique job identifier.
|
|
example: default-site-backup
|
|
command:
|
|
type: string
|
|
description: The command or callable to execute.
|
|
example: Grav\Common\Backup\Backups::backup
|
|
expression:
|
|
type: string
|
|
description: Cron expression for the schedule.
|
|
example: "0 3 * * *"
|
|
enabled:
|
|
type: boolean
|
|
description: Whether the job is enabled.
|
|
example: true
|
|
status:
|
|
type: string
|
|
enum: [success, error, pending]
|
|
description: Status of the last run.
|
|
example: success
|
|
last_run:
|
|
type: string
|
|
format: date-time
|
|
nullable: true
|
|
description: Timestamp of the last execution.
|
|
example: "2026-03-26T03:00:00+00:00"
|
|
error:
|
|
type: string
|
|
nullable: true
|
|
description: Error message from the last run, or `null` if successful.
|
|
example: null
|
|
required:
|
|
- id
|
|
- command
|
|
- expression
|
|
- enabled
|
|
- status
|
|
|
|
# -- Webhooks -------------------------------------------------------------
|
|
Webhook:
|
|
type: object
|
|
description: An outbound webhook configuration.
|
|
properties:
|
|
id:
|
|
type: string
|
|
description: Unique webhook identifier.
|
|
example: wh_abc123
|
|
url:
|
|
type: string
|
|
format: uri
|
|
description: Destination URL for deliveries.
|
|
example: https://example.com/hooks/grav
|
|
events:
|
|
type: array
|
|
items:
|
|
type: string
|
|
description: Event types subscribed to. Empty means all events.
|
|
example: [pages.created, pages.updated]
|
|
enabled:
|
|
type: boolean
|
|
description: Whether the webhook is active.
|
|
example: true
|
|
headers:
|
|
type: object
|
|
additionalProperties:
|
|
type: string
|
|
description: Custom HTTP headers included in deliveries.
|
|
example: {}
|
|
created:
|
|
type: string
|
|
format: date-time
|
|
description: Creation timestamp.
|
|
example: "2026-03-20T12:00:00+00:00"
|
|
last_delivery:
|
|
type: string
|
|
format: date-time
|
|
nullable: true
|
|
description: Timestamp of the most recent delivery, or `null` if never delivered.
|
|
example: "2026-03-25T14:30:00+00:00"
|
|
required:
|
|
- id
|
|
- url
|
|
- events
|
|
- enabled
|
|
- created
|
|
|
|
WebhookDelivery:
|
|
type: object
|
|
description: A record of a single webhook delivery attempt.
|
|
properties:
|
|
id:
|
|
type: string
|
|
description: Unique delivery identifier.
|
|
example: del_xyz789
|
|
webhook_id:
|
|
type: string
|
|
description: ID of the parent webhook.
|
|
example: wh_abc123
|
|
event:
|
|
type: string
|
|
description: The event type that triggered this delivery.
|
|
example: pages.updated
|
|
url:
|
|
type: string
|
|
format: uri
|
|
description: The URL the payload was delivered to.
|
|
example: https://example.com/hooks/grav
|
|
request_headers:
|
|
type: object
|
|
additionalProperties:
|
|
type: string
|
|
description: HTTP headers sent with the delivery.
|
|
request_body:
|
|
type: string
|
|
description: JSON payload sent in the request body.
|
|
response_status:
|
|
type: integer
|
|
nullable: true
|
|
description: HTTP status code returned by the receiver, or `null` if the request failed.
|
|
example: 200
|
|
response_body:
|
|
type: string
|
|
nullable: true
|
|
description: Response body from the receiver (truncated to 10 KB).
|
|
success:
|
|
type: boolean
|
|
description: Whether the delivery was successful (2xx response).
|
|
example: true
|
|
duration_ms:
|
|
type: integer
|
|
description: Request duration in milliseconds.
|
|
example: 234
|
|
delivered_at:
|
|
type: string
|
|
format: date-time
|
|
description: Timestamp of the delivery attempt.
|
|
example: "2026-03-25T14:30:00+00:00"
|
|
required:
|
|
- id
|
|
- webhook_id
|
|
- event
|
|
- url
|
|
- success
|
|
- delivered_at
|
|
|
|
# -- Dashboard ------------------------------------------------------------
|
|
Notification:
|
|
type: object
|
|
description: A dashboard notification.
|
|
properties:
|
|
id:
|
|
type: string
|
|
description: Unique notification identifier.
|
|
example: grav-update-1.7.49
|
|
type:
|
|
type: string
|
|
enum: [info, warning, error, success]
|
|
description: Notification severity level.
|
|
example: info
|
|
message:
|
|
type: string
|
|
description: Notification message text.
|
|
example: Grav 1.7.49 is now available.
|
|
date:
|
|
type: string
|
|
format: date-time
|
|
description: When the notification was created.
|
|
example: "2026-03-25T10:00:00+00:00"
|
|
location:
|
|
type: string
|
|
description: Where the notification should appear.
|
|
example: dashboard
|
|
link:
|
|
type: string
|
|
nullable: true
|
|
description: Optional URL for more information.
|
|
example: https://getgrav.org/blog/grav-1.7.49
|
|
read:
|
|
type: boolean
|
|
description: Whether the notification has been read/dismissed.
|
|
example: false
|
|
required:
|
|
- id
|
|
- type
|
|
- message
|
|
|
|
FeedItem:
|
|
type: object
|
|
description: A news feed item from the Grav blog or community.
|
|
properties:
|
|
title:
|
|
type: string
|
|
description: Article title.
|
|
example: Grav 1.7.49 Released
|
|
url:
|
|
type: string
|
|
format: uri
|
|
description: URL to the full article.
|
|
example: https://getgrav.org/blog/grav-1.7.49
|
|
date:
|
|
type: string
|
|
format: date-time
|
|
description: Publication date.
|
|
example: "2026-03-25T10:00:00+00:00"
|
|
excerpt:
|
|
type: string
|
|
description: Short excerpt or summary.
|
|
example: This release includes performance improvements and bug fixes.
|
|
required:
|
|
- title
|
|
- url
|
|
- date
|
|
|
|
DashboardStats:
|
|
type: object
|
|
description: Summary statistics for the dashboard overview.
|
|
properties:
|
|
pages:
|
|
type: integer
|
|
description: Total number of pages.
|
|
example: 42
|
|
users:
|
|
type: integer
|
|
description: Total number of user accounts.
|
|
example: 5
|
|
plugins:
|
|
type: integer
|
|
description: Number of installed plugins.
|
|
example: 12
|
|
theme:
|
|
type: string
|
|
description: Currently active theme slug.
|
|
example: quark
|
|
grav_version:
|
|
type: string
|
|
description: Installed Grav version.
|
|
example: 1.7.49
|
|
php_version:
|
|
type: string
|
|
description: PHP runtime version.
|
|
example: 8.3.14
|
|
last_backup:
|
|
type: string
|
|
format: date-time
|
|
nullable: true
|
|
description: Timestamp of the most recent backup, or `null` if no backups exist.
|
|
example: "2026-03-26T03:00:00+00:00"
|
|
required:
|
|
- pages
|
|
- users
|
|
- plugins
|
|
- theme
|
|
- grav_version
|
|
- php_version
|
|
|
|
# -- Batch ----------------------------------------------------------------
|
|
BatchResult:
|
|
type: object
|
|
description: Result of a single operation within a batch request.
|
|
properties:
|
|
route:
|
|
type: string
|
|
description: The page route that was operated on.
|
|
example: /blog/draft-one
|
|
status:
|
|
type: string
|
|
enum: [success, error]
|
|
description: Whether this individual operation succeeded.
|
|
example: success
|
|
message:
|
|
type: string
|
|
nullable: true
|
|
description: Error message if the operation failed, or `null` on success.
|
|
example: null
|
|
required:
|
|
- route
|
|
- status
|