Files
intotheeast-com-content/plugins/api/openapi.yaml
T

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