{
  "info": {
    "_postman_id": "grav-api-collection-v1",
    "name": "Grav CMS API",
    "description": "Complete API collection for the Grav CMS RESTful API plugin.\n\n## Setup\n\n1. Set the `base_url` variable to your Grav site URL (e.g., `https://localhost/grav-api`)\n2. Set the `api_key` variable to a valid API key (generate via `bin/plugin api keys:generate`)\n3. Set `grav_environment` to your Grav environment (default: `localhost`)\n4. All requests include `X-API-Key` and `X-Grav-Environment` headers automatically\n\n## Authentication\n\nAll requests (except `/auth/*` endpoints) require the `X-API-Key` header.\nThe `X-Grav-Environment` header is included on all requests to ensure correct environment context.",
    "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
  },
  "variable": [
    {
      "key": "base_url",
      "value": "http://localhost",
      "type": "string"
    },
    {
      "key": "api_key",
      "value": "grav_your_api_key_here",
      "type": "string"
    },
    {
      "key": "access_token",
      "value": "",
      "type": "string"
    },
    {
      "key": "refresh_token",
      "value": "",
      "type": "string"
    },
    {
      "key": "api_prefix",
      "value": "/api/v1",
      "type": "string"
    },
    {
      "key": "test_username",
      "value": "testuser",
      "type": "string"
    },
    {
      "key": "test_api_key_id",
      "value": "",
      "type": "string"
    },
    {
      "key": "grav_environment",
      "value": "localhost",
      "type": "string"
    },
    {
      "key": "page_route",
      "value": "blog",
      "type": "string"
    },
    {
      "key": "lang",
      "value": "en",
      "type": "string"
    },
    {
      "key": "package_slug",
      "value": "",
      "type": "string"
    },
    {
      "key": "webhook_id",
      "value": "",
      "type": "string"
    },
    {
      "key": "notification_id",
      "value": "",
      "type": "string"
    },
    {
      "key": "username",
      "value": "admin",
      "type": "string"
    },
    {
      "key": "password",
      "value": "",
      "type": "string"
    },
    {
      "key": "test_page_route",
      "value": "",
      "type": "string"
    }
  ],
  "item": [
    {
      "name": "Setup",
      "description": "Setup requests that run before all other tests. Creates test data and authenticates.",
      "item": [
        {
          "name": "Setup: Login",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "const res = pm.response.json();",
                  "pm.test(\"Status is 200\", () => pm.response.to.have.status(200));",
                  "pm.test(\"Has access_token\", () => pm.expect(res.data).to.have.property(\"access_token\"));",
                  "",
                  "if (res.data && res.data.access_token) {",
                  "    pm.collectionVariables.set(\"access_token\", res.data.access_token);",
                  "    pm.collectionVariables.set(\"refresh_token\", res.data.refresh_token);",
                  "}"
                ]
              }
            }
          ],
          "request": {
            "auth": {
              "type": "noauth"
            },
            "method": "POST",
            "header": [
              {
                "key": "X-Grav-Environment",
                "value": "{{grav_environment}}",
                "description": "Grav environment"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n    \"username\": \"{{username}}\",\n    \"password\": \"{{password}}\"\n}"
            },
            "url": {
              "raw": "{{base_url}}{{api_prefix}}/auth/token",
              "host": [
                "{{base_url}}{{api_prefix}}"
              ],
              "path": [
                "auth",
                "token"
              ]
            },
            "description": "Authenticate and save access_token and refresh_token to collection variables."
          }
        },
        {
          "name": "Setup: Create Test Page",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "const res = pm.response.json();",
                  "if (res.data && res.data.route) {",
                  "    pm.collectionVariables.set('test_page_route', res.data.route);",
                  "}",
                  "",
                  "pm.test('Status is 201', () => pm.response.to.have.status(201));",
                  "pm.test('Has route', () => pm.expect(res.data).to.have.property('route'));"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "X-API-Key",
                "value": "{{api_key}}",
                "description": "API Key authentication"
              },
              {
                "key": "X-Grav-Environment",
                "value": "{{grav_environment}}",
                "description": "Grav environment"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"route\": \"/newman-test-page\",\n  \"title\": \"Newman Test Page\",\n  \"template\": \"default\",\n  \"content\": \"# Test Page\\nCreated by Newman test runner.\"\n}"
            },
            "url": {
              "raw": "{{base_url}}{{api_prefix}}/pages",
              "host": [
                "{{base_url}}{{api_prefix}}"
              ],
              "path": [
                "pages"
              ]
            },
            "description": "Create a test page used by subsequent tests. Saves the route to test_page_route."
          }
        },
        {
          "name": "Setup: Create Test Webhook",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "const res = pm.response.json();",
                  "pm.test(\"Status is 201\", () => pm.response.to.have.status(201));",
                  "",
                  "if (res.data && res.data.id) {",
                  "    pm.collectionVariables.set(\"webhook_id\", res.data.id);",
                  "}"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "X-API-Key",
                "value": "{{api_key}}",
                "description": "API Key authentication"
              },
              {
                "key": "X-Grav-Environment",
                "value": "{{grav_environment}}",
                "description": "Grav environment"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n    \"url\": \"https://httpbin.org/post\",\n    \"events\": [\n        \"page.created\"\n    ],\n    \"secret\": \"newman-test-secret\"\n}"
            },
            "url": {
              "raw": "{{base_url}}{{api_prefix}}/webhooks",
              "host": [
                "{{base_url}}{{api_prefix}}"
              ],
              "path": [
                "webhooks"
              ]
            },
            "description": "Create a test webhook. Saves the webhook_id to collection variables."
          }
        }
      ]
    },
    {
      "name": "Auth",
      "description": "Authentication endpoints. These do not require prior authentication.",
      "item": [
        {
          "name": "Login (Get JWT Tokens)",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "const res = pm.response.json();",
                  "if (res.data && res.data.access_token) {",
                  "    pm.collectionVariables.set('access_token', res.data.access_token);",
                  "    pm.collectionVariables.set('refresh_token', res.data.refresh_token);",
                  "}",
                  "",
                  "pm.test('Status is 200', () => pm.response.to.have.status(200));",
                  "pm.test('Has access_token', () => pm.expect(res.data).to.have.property('access_token'));",
                  "pm.test('Has refresh_token', () => pm.expect(res.data).to.have.property('refresh_token'));",
                  "pm.test('Token type is Bearer', () => pm.expect(res.data.token_type).to.equal('Bearer'));",
                  "pm.test('Has expires_in', () => pm.expect(res.data.expires_in).to.be.a('number'));"
                ]
              }
            }
          ],
          "request": {
            "auth": {
              "type": "noauth"
            },
            "method": "POST",
            "header": [
              {
                "key": "X-Grav-Environment",
                "value": "{{grav_environment}}",
                "description": "Grav environment"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n    \"username\": \"{{username}}\",\n    \"password\": \"{{password}}\"\n}"
            },
            "url": {
              "raw": "{{base_url}}{{api_prefix}}/auth/token",
              "host": [
                "{{base_url}}{{api_prefix}}"
              ],
              "path": [
                "auth",
                "token"
              ]
            },
            "description": "Exchange username/password for JWT access and refresh tokens. The access token is automatically saved for use by other requests."
          }
        },
        {
          "name": "Refresh Token",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "const res = pm.response.json();",
                  "if (res.data && res.data.access_token) {",
                  "    pm.collectionVariables.set('access_token', res.data.access_token);",
                  "    pm.collectionVariables.set('refresh_token', res.data.refresh_token);",
                  "}",
                  "",
                  "pm.test('Status is 200', () => pm.response.to.have.status(200));",
                  "pm.test('New tokens issued', () => pm.expect(res.data).to.have.property('access_token'));"
                ]
              }
            }
          ],
          "request": {
            "auth": {
              "type": "noauth"
            },
            "method": "POST",
            "header": [
              {
                "key": "X-Grav-Environment",
                "value": "{{grav_environment}}",
                "description": "Grav environment"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n    \"refresh_token\": \"{{refresh_token}}\"\n}"
            },
            "url": {
              "raw": "{{base_url}}{{api_prefix}}/auth/refresh",
              "host": [
                "{{base_url}}{{api_prefix}}"
              ],
              "path": [
                "auth",
                "refresh"
              ]
            },
            "description": "Exchange a refresh token for a new access/refresh token pair. The old refresh token is revoked (token rotation)."
          }
        },
        {
          "name": "Revoke Token",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('Status is 204', () => pm.response.to.have.status(204));"
                ]
              }
            }
          ],
          "request": {
            "auth": {
              "type": "noauth"
            },
            "method": "POST",
            "header": [
              {
                "key": "X-Grav-Environment",
                "value": "{{grav_environment}}",
                "description": "Grav environment"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n    \"refresh_token\": \"{{refresh_token}}\"\n}"
            },
            "url": {
              "raw": "{{base_url}}{{api_prefix}}/auth/revoke",
              "host": [
                "{{base_url}}{{api_prefix}}"
              ],
              "path": [
                "auth",
                "revoke"
              ]
            },
            "description": "Revoke a refresh token, preventing it from being used to generate new access tokens."
          }
        },
        {
          "id": "openapi:getCaptchaConfig",
          "name": "Discover the login captcha",
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "// Generated from openapi.yaml by tests/newman/sync-collection.php; the Newman suite skips it.",
                  "if (pm.variables.get('newman_suite')) { pm.execution.skipRequest(); }"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "X-Grav-Environment",
                "value": "{{grav_environment}}",
                "description": "Grav environment"
              }
            ],
            "url": {
              "raw": "{{base_url}}{{api_prefix}}/auth/captcha",
              "host": [
                "{{base_url}}{{api_prefix}}"
              ],
              "path": [
                "auth",
                "captcha"
              ]
            },
            "description": "Report which captcha (if any) guards the unauthenticated auth endpoints,\nso a client can render the right challenge before submitting. Public,\nand never returns a secret."
          }
        },
        {
          "id": "openapi:createCaptchaChallenge",
          "name": "Mint a proof-of-work challenge",
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "// Generated from openapi.yaml by tests/newman/sync-collection.php; the Newman suite skips it.",
                  "if (pm.variables.get('newman_suite')) { pm.execution.skipRequest(); }"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "X-Grav-Environment",
                "value": "{{grav_environment}}",
                "description": "Grav environment"
              }
            ],
            "url": {
              "raw": "{{base_url}}{{api_prefix}}/auth/captcha/challenge",
              "host": [
                "{{base_url}}{{api_prefix}}"
              ],
              "path": [
                "auth",
                "captcha",
                "challenge"
              ]
            },
            "description": "Issue a cap.js challenge for the client to solve. Only available while\n`cap` is the active provider; 404 otherwise. The response is the cap.js\nwire format, not this API's `data` envelope.\n\n\nPublic: no authentication required."
          }
        },
        {
          "id": "openapi:redeemCaptchaChallenge",
          "name": "Exchange solutions for a captcha token",
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "// Generated from openapi.yaml by tests/newman/sync-collection.php; the Newman suite skips it.",
                  "if (pm.variables.get('newman_suite')) { pm.execution.skipRequest(); }"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "X-Grav-Environment",
                "value": "{{grav_environment}}",
                "description": "Grav environment"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url}}{{api_prefix}}/auth/captcha/redeem",
              "host": [
                "{{base_url}}{{api_prefix}}"
              ],
              "path": [
                "auth",
                "captcha",
                "redeem"
              ]
            },
            "description": "Trade the solved sub-challenges for a single-use token to send with the\nlogin, forgotten-password or setup request. Only available while `cap`\nis the active provider; 404 otherwise. The response is the cap.js wire\nformat, not this API's `data` envelope.\n\n\nPublic: no authentication required.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"token\": \"\",\n  \"solutions\": [\n    0\n  ]\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "id": "openapi:verifyTwoFactorLogin",
          "name": "Complete a 2FA login",
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "// Generated from openapi.yaml by tests/newman/sync-collection.php; the Newman suite skips it.",
                  "if (pm.variables.get('newman_suite')) { pm.execution.skipRequest(); }"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "X-Grav-Environment",
                "value": "{{grav_environment}}",
                "description": "Grav environment"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url}}{{api_prefix}}/auth/2fa/verify",
              "host": [
                "{{base_url}}{{api_prefix}}"
              ],
              "path": [
                "auth",
                "2fa",
                "verify"
              ]
            },
            "description": "Second step of a login for an account with two-factor authentication enabled. `POST /auth/token` (or an SSO exchange) returns a `requires_2fa` challenge instead of tokens; send its `challenge_token` here with the current TOTP code to receive the token pair. The challenge token is burned on success so it cannot be replayed.\nPublic. Shares the login rate limiter with `POST /auth/token`, keyed by the challenged username.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"challenge_token\": \"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...\",\n  \"code\": \"123456\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "id": "openapi:requestPasswordReset",
          "name": "Request a password reset email",
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "// Generated from openapi.yaml by tests/newman/sync-collection.php; the Newman suite skips it.",
                  "if (pm.variables.get('newman_suite')) { pm.execution.skipRequest(); }"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "X-Grav-Environment",
                "value": "{{grav_environment}}",
                "description": "Grav environment"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url}}{{api_prefix}}/auth/forgot-password",
              "host": [
                "{{base_url}}{{api_prefix}}"
              ],
              "path": [
                "auth",
                "forgot-password"
              ]
            },
            "description": "Send a password reset link to the account that owns `email`. The response is the same neutral message whether or not the address matches an account, whether the Email and Login plugins are configured, and whether sending failed, so it cannot be used to find accounts.\nThe link points at the admin's own `/reset` route. Its base is taken from `admin_base_url`, then the `Referer`, then the `Origin` header, and each candidate is accepted only when its origin is the site's own or is listed in the API's `cors.origins`; anything else falls back to the site's root URL. The reset token is valid for 24 hours.\nPublic. When the site has a login captcha that covers the forgotten-password flow, the solved token is required (see `GET /auth/captcha`). Repeated requests for one account are limited by the Login plugin's `pw_resets` bucket.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"email\": \"jane@example.com\",\n  \"admin_base_url\": \"https://example.com/admin\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "id": "openapi:resetPassword",
          "name": "Set a new password with a reset token",
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "// Generated from openapi.yaml by tests/newman/sync-collection.php; the Newman suite skips it.",
                  "if (pm.variables.get('newman_suite')) { pm.execution.skipRequest(); }"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "X-Grav-Environment",
                "value": "{{grav_environment}}",
                "description": "Grav environment"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url}}{{api_prefix}}/auth/reset-password",
              "host": [
                "{{base_url}}{{api_prefix}}"
              ],
              "path": [
                "auth",
                "reset-password"
              ]
            },
            "description": "Complete a password reset using the `user` and `token` values from the reset link. Every failure (unknown user, wrong token, expired token) returns the same 422 message so tokens cannot be probed. Once the link is valid, the new password must meet the site's password policy (`system.pwd_regex`, or at least 8 characters when none is set; see `GET /auth/password-policy`), otherwise a 422 names the `password` field and the link stays usable. On success every access and refresh token issued to the account before the reset stops working.\nPublic. Rate limited per username through the Login plugin's login bucket.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"username\": \"jane\",\n  \"token\": \"5f4dcc3b5aa765d61d8327deb882cf99\",\n  \"password\": \"N3w-Pa$$w0rd\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "id": "openapi:getPasswordPolicy",
          "name": "Read the password policy",
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "// Generated from openapi.yaml by tests/newman/sync-collection.php; the Newman suite skips it.",
                  "if (pm.variables.get('newman_suite')) { pm.execution.skipRequest(); }"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "X-Grav-Environment",
                "value": "{{grav_environment}}",
                "description": "Grav environment"
              }
            ],
            "url": {
              "raw": "{{base_url}}{{api_prefix}}/auth/password-policy",
              "host": [
                "{{base_url}}{{api_prefix}}"
              ],
              "path": [
                "auth",
                "password-policy"
              ]
            },
            "description": "Return the site's password policy (`system.pwd_regex`, plus the checklist rules from `system.pwd_rules` or parsed from the regex) so setup, reset and user-creation forms can validate as the user types. With no `pwd_regex` set, the API still requires at least 8 characters, so the response reports `min_length: 8` and a single length rule. The same policy is enforced by setup, invite accept, password reset, and user create/update. Public."
          }
        },
        {
          "id": "openapi:getSetupStatus",
          "name": "Check whether first-run setup is needed",
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "// Generated from openapi.yaml by tests/newman/sync-collection.php; the Newman suite skips it.",
                  "if (pm.variables.get('newman_suite')) { pm.execution.skipRequest(); }"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "X-Grav-Environment",
                "value": "{{grav_environment}}",
                "description": "Grav environment"
              }
            ],
            "url": {
              "raw": "{{base_url}}{{api_prefix}}/auth/setup",
              "host": [
                "{{base_url}}{{api_prefix}}"
              ],
              "path": [
                "auth",
                "setup"
              ]
            },
            "description": "Report whether the site has no user accounts yet, in which case the admin shows its create-the-first-account screen. Also returns the password policy so that screen can validate without a second call. Public."
          }
        },
        {
          "id": "openapi:completeSetup",
          "name": "Create the first super-admin account",
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "// Generated from openapi.yaml by tests/newman/sync-collection.php; the Newman suite skips it.",
                  "if (pm.variables.get('newman_suite')) { pm.execution.skipRequest(); }"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "X-Grav-Environment",
                "value": "{{grav_environment}}",
                "description": "Grav environment"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url}}{{api_prefix}}/auth/setup",
              "host": [
                "{{base_url}}{{api_prefix}}"
              ],
              "path": [
                "auth",
                "setup"
              ]
            },
            "description": "Create the site's first account with `api.super` and `site.login` access, then log it in and return a token pair. Only works while no accounts exist; afterwards it returns 409.\nThe username must be 3 to 64 characters, may not start with a period or contain `..`, and may not contain `\\ / ? * : ; { }` or a line break. The password must match `system.pwd_regex`, or be at least 8 characters when no regex is set.\nPublic. Rate limited per IP address, and requires a solved captcha when the site's login captcha covers the setup flow.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"username\": \"admin\",\n  \"password\": \"Pa$$w0rd!\",\n  \"email\": \"admin@example.com\",\n  \"fullname\": \"Site Administrator\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "id": "openapi:validateInvitation",
          "name": "Check an invitation link",
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "// Generated from openapi.yaml by tests/newman/sync-collection.php; the Newman suite skips it.",
                  "if (pm.variables.get('newman_suite')) { pm.execution.skipRequest(); }"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "X-Grav-Environment",
                "value": "{{grav_environment}}",
                "description": "Grav environment"
              }
            ],
            "url": {
              "raw": "{{base_url}}{{api_prefix}}/auth/invite/:token",
              "host": [
                "{{base_url}}{{api_prefix}}"
              ],
              "path": [
                "auth",
                "invite",
                ":token"
              ],
              "variable": [
                {
                  "key": "token",
                  "value": "",
                  "description": "The invitation token from the emailed invite link."
                }
              ]
            },
            "description": "Look up an invitation so the accept screen can show who it is for. An expired invitation still returns 200, with `valid: false` and `expired: true`; an unknown token returns 404. Public: the token is the only credential."
          }
        },
        {
          "id": "openapi:acceptInvitation",
          "name": "Accept an invitation",
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "// Generated from openapi.yaml by tests/newman/sync-collection.php; the Newman suite skips it.",
                  "if (pm.variables.get('newman_suite')) { pm.execution.skipRequest(); }"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "X-Grav-Environment",
                "value": "{{grav_environment}}",
                "description": "Grav environment"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url}}{{api_prefix}}/auth/invite/:token",
              "host": [
                "{{base_url}}{{api_prefix}}"
              ],
              "path": [
                "auth",
                "invite",
                ":token"
              ],
              "variable": [
                {
                  "key": "token",
                  "value": "",
                  "description": "The invitation token from the emailed invite link."
                }
              ]
            },
            "description": "Create the invited account and log it in. The email address, access and groups come from the invitation and cannot be changed by the request. The username must be 3 to 64 characters, may not start with a period or contain `..`, and may not contain `\\ / ? * : ; { }` or a line break; the password must match `system.pwd_regex`, or be at least 8 characters when no regex is set. The invitation is consumed on success, and an expired one is deleted and answered with 410.\nPublic: the token is the only credential.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"username\": \"jane\",\n  \"password\": \"Pa$$w0rd!\",\n  \"fullname\": \"Jane Doe\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "id": "openapi:listSsoProviders",
          "name": "List SSO login providers",
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "// Generated from openapi.yaml by tests/newman/sync-collection.php; the Newman suite skips it.",
                  "if (pm.variables.get('newman_suite')) { pm.execution.skipRequest(); }"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "X-Grav-Environment",
                "value": "{{grav_environment}}",
                "description": "Grav environment"
              }
            ],
            "url": {
              "raw": "{{base_url}}{{api_prefix}}/auth/sso/providers",
              "host": [
                "{{base_url}}{{api_prefix}}"
              ],
              "path": [
                "auth",
                "sso",
                "providers"
              ]
            },
            "description": "Return the single sign-on buttons to show on the login screen, collected from every plugin that answers the `onApiLoginProviders` event (for example Login OAuth2). An empty list means password login only. Public."
          }
        },
        {
          "id": "openapi:startSsoLogin",
          "name": "Begin an SSO login",
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "// Generated from openapi.yaml by tests/newman/sync-collection.php; the Newman suite skips it.",
                  "if (pm.variables.get('newman_suite')) { pm.execution.skipRequest(); }"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "X-Grav-Environment",
                "value": "{{grav_environment}}",
                "description": "Grav environment"
              }
            ],
            "url": {
              "raw": "{{base_url}}{{api_prefix}}/auth/sso/:provider/start",
              "host": [
                "{{base_url}}{{api_prefix}}"
              ],
              "path": [
                "auth",
                "sso",
                ":provider",
                "start"
              ],
              "query": [
                {
                  "key": "returnTo",
                  "value": "",
                  "description": "In-app path to land on after login. Only a root-relative path is kept; absolute URLs, `//host` and control characters are dropped.",
                  "disabled": true
                }
              ],
              "variable": [
                {
                  "key": "provider",
                  "value": "",
                  "description": "Provider id from `GET /auth/sso/providers`."
                }
              ]
            },
            "description": "Browser navigation, not an XHR call. Stores the provider's CSRF state and the admin's return target in the PHP session, then redirects to the provider's authorization URL built by the plugin that answers `onApiLoginStart`. When no plugin handles the provider, redirects back to the admin's `/login?sso_error=<code>` instead. This route keeps its session cookie so the callback can read the state back. Public."
          }
        },
        {
          "id": "openapi:handleSsoCallback",
          "name": "Receive the SSO provider's response",
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "// Generated from openapi.yaml by tests/newman/sync-collection.php; the Newman suite skips it.",
                  "if (pm.variables.get('newman_suite')) { pm.execution.skipRequest(); }"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "X-Grav-Environment",
                "value": "{{grav_environment}}",
                "description": "Grav environment"
              }
            ],
            "url": {
              "raw": "{{base_url}}{{api_prefix}}/auth/sso/:provider/callback",
              "host": [
                "{{base_url}}{{api_prefix}}"
              ],
              "path": [
                "auth",
                "sso",
                ":provider",
                "callback"
              ],
              "variable": [
                {
                  "key": "provider",
                  "value": "",
                  "description": "Provider id."
                }
              ]
            },
            "description": "The provider redirects the browser here. The plugin that answers `onApiLoginCallback` validates the state and resolves a Grav user; the same API-access, account-state and 2FA checks as password login then run. The resulting token pair or 2FA challenge is stored under a single-use code (valid for 120 seconds) and the browser is redirected to `<admin>/oauth-callback?code=<code>[&returnTo=<path>]`, where the admin trades it via `POST /auth/sso/exchange`. On failure the browser is sent to `<admin>/login?sso_error=<code>` (`sso_failed`, `sso_forbidden`, or a provider-specific code). Public."
          }
        },
        {
          "id": "openapi:exchangeSsoCode",
          "name": "Trade an SSO code for tokens",
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "// Generated from openapi.yaml by tests/newman/sync-collection.php; the Newman suite skips it.",
                  "if (pm.variables.get('newman_suite')) { pm.execution.skipRequest(); }"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "X-Grav-Environment",
                "value": "{{grav_environment}}",
                "description": "Grav environment"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url}}{{api_prefix}}/auth/sso/exchange",
              "host": [
                "{{base_url}}{{api_prefix}}"
              ],
              "path": [
                "auth",
                "sso",
                "exchange"
              ]
            },
            "description": "Exchange the one-time code from the SSO callback redirect for the stored login result: the same body `POST /auth/token` returns, either a token pair or a `requires_2fa` challenge to finish with `POST /auth/2fa/verify`. The code is deleted on first use. Public.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"code\": \"9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        }
      ]
    },
    {
      "name": "Pages",
      "description": "Content management — create, read, update, delete, move, and copy pages.",
      "item": [
        {
          "name": "List Pages",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "const res = pm.response.json();",
                  "pm.test('Status is 200', () => pm.response.to.have.status(200));",
                  "pm.test('Has data array', () => pm.expect(res.data).to.be.an('array'));",
                  "pm.test('Has pagination meta', () => pm.expect(res.meta.pagination).to.have.property('total'));",
                  "pm.test('Has links', () => pm.expect(res.links).to.have.property('self'));"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "url": {
              "raw": "{{base_url}}{{api_prefix}}/pages?page=1&per_page=20",
              "host": [
                "{{base_url}}{{api_prefix}}"
              ],
              "path": [
                "pages"
              ],
              "query": [
                {
                  "key": "page",
                  "value": "1"
                },
                {
                  "key": "per_page",
                  "value": "20"
                },
                {
                  "key": "sort",
                  "value": "date",
                  "disabled": true
                },
                {
                  "key": "order",
                  "value": "desc",
                  "disabled": true
                },
                {
                  "key": "published",
                  "value": "true",
                  "disabled": true
                },
                {
                  "key": "template",
                  "value": "post",
                  "disabled": true
                },
                {
                  "key": "routable",
                  "value": "true",
                  "disabled": true
                },
                {
                  "key": "visible",
                  "value": "true",
                  "disabled": true
                },
                {
                  "key": "parent",
                  "value": "blog",
                  "disabled": true
                }
              ]
            },
            "description": "List all pages with filtering, sorting, and pagination.\n\n**Filters** (enable as needed):\n- `published` - true/false\n- `template` - page template name\n- `routable` - true/false\n- `visible` - true/false\n- `parent` - parent route prefix\n\n**Sorting**: `sort` (date, title, slug, modified, order) + `order` (asc, desc)",
            "header": [
              {
                "key": "X-API-Key",
                "value": "{{api_key}}",
                "description": "API Key authentication"
              },
              {
                "key": "X-Grav-Environment",
                "value": "{{grav_environment}}",
                "description": "Grav environment"
              }
            ]
          }
        },
        {
          "name": "List Pages (Filtered: Blog Posts)",
          "request": {
            "method": "GET",
            "url": {
              "raw": "{{base_url}}{{api_prefix}}/pages?published=true&template=post&parent=blog&sort=date&order=desc",
              "host": [
                "{{base_url}}{{api_prefix}}"
              ],
              "path": [
                "pages"
              ],
              "query": [
                {
                  "key": "published",
                  "value": "true"
                },
                {
                  "key": "template",
                  "value": "post"
                },
                {
                  "key": "parent",
                  "value": "blog"
                },
                {
                  "key": "sort",
                  "value": "date"
                },
                {
                  "key": "order",
                  "value": "desc"
                }
              ]
            },
            "description": "Example: List published blog posts, newest first.",
            "header": [
              {
                "key": "X-API-Key",
                "value": "{{api_key}}",
                "description": "API Key authentication"
              },
              {
                "key": "X-Grav-Environment",
                "value": "{{grav_environment}}",
                "description": "Grav environment"
              }
            ]
          }
        },
        {
          "name": "Get Page",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "const res = pm.response.json();",
                  "pm.test('Status is 200', () => pm.response.to.have.status(200));",
                  "pm.test('Has route', () => pm.expect(res.data).to.have.property('route'));",
                  "pm.test('Has title', () => pm.expect(res.data).to.have.property('title'));",
                  "pm.test('Has content', () => pm.expect(res.data).to.have.property('content'));",
                  "pm.test('Has ETag header', () => pm.response.to.have.header('ETag'));"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "url": {
              "raw": "{{base_url}}{{api_prefix}}/pages/blog",
              "host": [
                "{{base_url}}{{api_prefix}}"
              ],
              "path": [
                "pages",
                "blog"
              ]
            },
            "description": "Get a single page by route. Returns full content, frontmatter, taxonomy, and media list.",
            "header": [
              {
                "key": "X-API-Key",
                "value": "{{api_key}}",
                "description": "API Key authentication"
              },
              {
                "key": "X-Grav-Environment",
                "value": "{{grav_environment}}",
                "description": "Grav environment"
              }
            ]
          }
        },
        {
          "name": "Get Page (Rendered HTML + Children)",
          "request": {
            "method": "GET",
            "url": {
              "raw": "{{base_url}}{{api_prefix}}/pages/blog?render=true&children=true&children_depth=2",
              "host": [
                "{{base_url}}{{api_prefix}}"
              ],
              "path": [
                "pages",
                "blog"
              ],
              "query": [
                {
                  "key": "render",
                  "value": "true"
                },
                {
                  "key": "children",
                  "value": "true"
                },
                {
                  "key": "children_depth",
                  "value": "2"
                }
              ]
            },
            "description": "Get a page with rendered HTML content and child pages up to 2 levels deep.",
            "header": [
              {
                "key": "X-API-Key",
                "value": "{{api_key}}",
                "description": "API Key authentication"
              },
              {
                "key": "X-Grav-Environment",
                "value": "{{grav_environment}}",
                "description": "Grav environment"
              }
            ]
          }
        },
        {
          "name": "Create Page",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "const res = pm.response.json();",
                  "pm.test('Status is 201', () => pm.response.to.have.status(201));",
                  "pm.test('Has Location header', () => pm.response.to.have.header('Location'));",
                  "pm.test('Has route', () => pm.expect(res.data).to.have.property('route'));"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "X-API-Key",
                "value": "{{api_key}}",
                "description": "API Key authentication"
              },
              {
                "key": "X-Grav-Environment",
                "value": "{{grav_environment}}",
                "description": "Grav environment"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n    \"route\": \"/newman-test-create-{{$timestamp}}\",\n    \"title\": \"Newman Created Page\",\n    \"template\": \"default\",\n    \"content\": \"# Test Page\\n\\nCreated by Newman with timestamp.\",\n    \"header\": {\n        \"taxonomy\": {\n            \"category\": [\"test\"],\n            \"tag\": [\"api\", \"newman\"]\n        },\n        \"published\": true\n    }\n}"
            },
            "url": {
              "raw": "{{base_url}}{{api_prefix}}/pages",
              "host": [
                "{{base_url}}{{api_prefix}}"
              ],
              "path": [
                "pages"
              ]
            },
            "description": "Create a new page. Requires `route` and `title`. Optional: `template`, `content`, `header` (frontmatter), `order`."
          }
        },
        {
          "name": "Update Page",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "const res = pm.response.json();",
                  "pm.test('Status is 200', () => pm.response.to.have.status(200));",
                  "pm.test('Has updated content', () => pm.expect(res.data).to.have.property('content'));"
                ]
              }
            }
          ],
          "request": {
            "method": "PATCH",
            "header": [
              {
                "key": "X-API-Key",
                "value": "{{api_key}}",
                "description": "API Key authentication"
              },
              {
                "key": "X-Grav-Environment",
                "value": "{{grav_environment}}",
                "description": "Grav environment"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "If-Match",
                "value": "\"etag-from-get-request\"",
                "description": "Optional: ETag from a previous GET for conflict detection",
                "disabled": true
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n    \"title\": \"Updated Blog Post Title\",\n    \"content\": \"# Updated Content\\n\\nThis content was updated via the API.\",\n    \"header\": {\n        \"taxonomy\": {\n            \"tag\": [\"api\", \"grav\", \"updated\"]\n        }\n    }\n}"
            },
            "url": {
              "raw": "{{base_url}}{{api_prefix}}/pages/newman-test-page",
              "host": [
                "{{base_url}}{{api_prefix}}"
              ],
              "path": [
                "pages",
                "test",
                "newman-page"
              ]
            },
            "description": "Partial update — only send the fields you want to change. Supports `content`, `title`, `header`, `template`, `published`, `visible`.\n\nEnable the `If-Match` header for optimistic concurrency control."
          }
        },
        {
          "name": "Move Page",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('Status is 200', () => pm.response.to.have.status(200));"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "X-API-Key",
                "value": "{{api_key}}",
                "description": "API Key authentication"
              },
              {
                "key": "X-Grav-Environment",
                "value": "{{grav_environment}}",
                "description": "Grav environment"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"parent\": \"/\",\n  \"slug\": \"newman-moved-page\"\n}"
            },
            "url": {
              "raw": "{{base_url}}{{api_prefix}}/pages/newman-test-page/move",
              "host": [
                "{{base_url}}{{api_prefix}}/pages/newman-test-page/move"
              ],
              "path": [
                "pages",
                "test",
                "newman-page",
                "move"
              ]
            },
            "description": "Move a page to a new parent and/or rename its slug. Requires `parent`. Optional: `slug`, `order`."
          }
        },
        {
          "name": "Copy Page",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('Status is 201', () => pm.response.to.have.status(201));",
                  "pm.test('Has Location header', () => pm.response.to.have.header('Location'));"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "X-API-Key",
                "value": "{{api_key}}",
                "description": "API Key authentication"
              },
              {
                "key": "X-Grav-Environment",
                "value": "{{grav_environment}}",
                "description": "Grav environment"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n    \"route\": \"/newman-test-copy\"\n}"
            },
            "url": {
              "raw": "{{base_url}}{{api_prefix}}/pages/newman-test-page/copy",
              "host": [
                "{{base_url}}{{api_prefix}}"
              ],
              "path": [
                "pages",
                "test",
                "newman-page",
                "copy"
              ]
            },
            "description": "Copy a page (and its media) to a new route."
          }
        },
        {
          "name": "Delete Page",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('Status is 204', () => pm.response.to.have.status(204));"
                ]
              }
            }
          ],
          "request": {
            "method": "DELETE",
            "url": {
              "raw": "{{base_url}}{{api_prefix}}/pages/newman-test-copy?children=true",
              "host": [
                "{{base_url}}{{api_prefix}}"
              ],
              "path": [
                "pages",
                "test",
                "newman-copy"
              ],
              "query": [
                {
                  "key": "children",
                  "value": "true",
                  "description": "Set to true to also delete child pages"
                }
              ]
            },
            "description": "Delete a page. If the page has children and `children=true` is not set, returns a 422 error requiring confirmation.",
            "header": [
              {
                "key": "X-API-Key",
                "value": "{{api_key}}",
                "description": "API Key authentication"
              },
              {
                "key": "X-Grav-Environment",
                "value": "{{grav_environment}}",
                "description": "Grav environment"
              }
            ]
          }
        },
        {
          "name": "List Taxonomy",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "const res = pm.response.json();",
                  "pm.test('Status is 200', () => pm.response.to.have.status(200));",
                  "pm.test('Has data', () => pm.expect(res.data).to.be.an('object'));"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "url": {
              "raw": "{{base_url}}{{api_prefix}}/taxonomy",
              "host": [
                "{{base_url}}{{api_prefix}}"
              ],
              "path": [
                "taxonomy"
              ]
            },
            "description": "List all taxonomy types (categories, tags, etc.) and their values across all pages.",
            "header": [
              {
                "key": "X-API-Key",
                "value": "{{api_key}}",
                "description": "API Key authentication"
              },
              {
                "key": "X-Grav-Environment",
                "value": "{{grav_environment}}",
                "description": "Grav environment"
              }
            ]
          }
        },
        {
          "name": "List Root Children (Lazy Load)",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('Status is 200', () => pm.response.to.have.status(200));",
                  "pm.test('Returns array', () => pm.expect(pm.response.json().data).to.be.an('array'));",
                  "pm.test('Pages have has_children field', () => {",
                  "    const pages = pm.response.json().data;",
                  "    if (pages.length > 0) {",
                  "        pm.expect(pages[0]).to.have.property('has_children');",
                  "    }",
                  "});"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "X-API-Key",
                "value": "{{api_key}}"
              },
              {
                "key": "X-Grav-Environment",
                "value": "{{grav_environment}}"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url}}{{api_prefix}}/pages?children_of=/&sort=order",
              "host": [
                "{{base_url}}{{api_prefix}}/pages?children_of=/&sort=order"
              ]
            },
            "description": "Get only root-level pages (direct children of /). Returns has_children boolean for each page. Ideal for lazy-loading tree views and miller columns."
          },
          "response": []
        },
        {
          "name": "List Children of /blog",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('Status is 200', () => pm.response.to.have.status(200));",
                  "pm.test('All pages are direct children of /blog', () => {",
                  "    const pages = pm.response.json().data;",
                  "    pages.forEach(p => {",
                  "        pm.expect(p.route).to.match(/^\\/blog\\/[^\\/]+$/);",
                  "    });",
                  "});"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "X-API-Key",
                "value": "{{api_key}}"
              },
              {
                "key": "X-Grav-Environment",
                "value": "{{grav_environment}}"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url}}{{api_prefix}}/pages?children_of=/blog&sort=order",
              "host": [
                "{{base_url}}{{api_prefix}}/pages?children_of=/blog&sort=order"
              ]
            },
            "description": "Get direct children of /blog only (one level deep). Unlike parent filter, children_of does not return deeper descendants."
          },
          "response": []
        },
        {
          "id": "openapi:comparePageLanguages",
          "name": "Compare two language versions of a page",
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "// Generated from openapi.yaml by tests/newman/sync-collection.php; the Newman suite skips it.",
                  "if (pm.variables.get('newman_suite')) { pm.execution.skipRequest(); }"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "X-API-Key",
                "value": "{{api_key}}",
                "description": "API Key authentication"
              },
              {
                "key": "X-Grav-Environment",
                "value": "{{grav_environment}}",
                "description": "Grav environment"
              }
            ],
            "url": {
              "raw": "{{base_url}}{{api_prefix}}/pages/:route/compare?source=&target=",
              "host": [
                "{{base_url}}{{api_prefix}}"
              ],
              "path": [
                "pages",
                ":route",
                "compare"
              ],
              "query": [
                {
                  "key": "source",
                  "value": "",
                  "description": "Source language code.",
                  "disabled": false
                },
                {
                  "key": "target",
                  "value": "",
                  "description": "Target language code.",
                  "disabled": false
                }
              ],
              "variable": [
                {
                  "key": "route",
                  "value": "{{page_route}}",
                  "description": "The page route without leading slash (e.g. `blog/my-post`). Supports\nnested routes with `/` separators.\n"
                }
              ]
            },
            "description": "Return the title, raw Markdown, frontmatter and modified date of a page in two languages side by side. A side is `null` when the page does not resolve in that language, and `exists` is false when the page resolves only through the default-language fallback rather than its own file. Needs multi-language enabled (422 otherwise). Requires `api.pages.read`, and neither the source nor the target page may deny the caller read access. A language code that is not a string (for example `source[]=`) is a 422."
          }
        },
        {
          "id": "openapi:getPageNeighbors",
          "name": "Get a page's parent, siblings and first child",
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "// Generated from openapi.yaml by tests/newman/sync-collection.php; the Newman suite skips it.",
                  "if (pm.variables.get('newman_suite')) { pm.execution.skipRequest(); }"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "X-API-Key",
                "value": "{{api_key}}",
                "description": "API Key authentication"
              },
              {
                "key": "X-Grav-Environment",
                "value": "{{grav_environment}}",
                "description": "Grav environment"
              }
            ],
            "url": {
              "raw": "{{base_url}}{{api_prefix}}/pages/:route/neighbors",
              "host": [
                "{{base_url}}{{api_prefix}}"
              ],
              "path": [
                "pages",
                ":route",
                "neighbors"
              ],
              "query": [
                {
                  "key": "lang",
                  "value": "",
                  "description": "Two-letter language code. When provided, the request operates on\nthe page translation for that language. If omitted, the default\nsite language is used.\n",
                  "disabled": true
                },
                {
                  "key": "translations",
                  "value": "false",
                  "description": "When `true`, each row also carries the translation fields, as on `GET /pages`.",
                  "disabled": true
                }
              ],
              "variable": [
                {
                  "key": "route",
                  "value": "{{page_route}}",
                  "description": "The page route without leading slash (e.g. `blog/my-post`). Supports\nnested routes with `/` separators.\n"
                }
              ]
            },
            "description": "Where a page sits among its siblings, for page-to-page navigation:\nits parent, the previous and next sibling, its first child, its\nzero-based position and the number of siblings.\n\nSiblings are the parent's children in exactly the order\n`GET /pages?children_of=<parent>&sort=default&order=asc` lists them\n(`children_of=/` for a top-level page), and `first_child` is the first\nrow of the same listing for the page's own children. Every row is\nserialized exactly like a `GET /pages?fields=summary` row, including\n`permissions`. Only the parent's and the page's own children are read,\nnever the whole site.\n\n`parent` is null for a top-level page, `prev` for the first sibling,\n`next` for the last and `first_child` when the page has no children.\nA page that its parent's listing leaves out (a folder with no content\nfile) gets `index: -1` and null `prev` and `next`.\n\nPages the caller can't read on their own route are left out first:\n`parent` is null when the parent is unreadable, `prev`, `next` and\n`first_child` are the nearest readable pages, and `index` and `total`\ncount only readable siblings.\n\nRequires `api.pages.read` (or a page-level read grant), and the page's\nown rules must allow reading it. An unknown route is a 404."
          }
        },
        {
          "id": "openapi:syncPageTranslation",
          "name": "Overwrite a translation from another language",
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "// Generated from openapi.yaml by tests/newman/sync-collection.php; the Newman suite skips it.",
                  "if (pm.variables.get('newman_suite')) { pm.execution.skipRequest(); }"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "X-API-Key",
                "value": "{{api_key}}",
                "description": "API Key authentication"
              },
              {
                "key": "X-Grav-Environment",
                "value": "{{grav_environment}}",
                "description": "Grav environment"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url}}{{api_prefix}}/pages/:route/sync",
              "host": [
                "{{base_url}}{{api_prefix}}"
              ],
              "path": [
                "pages",
                ":route",
                "sync"
              ],
              "variable": [
                {
                  "key": "route",
                  "value": "{{page_route}}",
                  "description": "The page route without leading slash (e.g. `blog/my-post`). Supports\nnested routes with `/` separators.\n"
                }
              ]
            },
            "description": "Copy the frontmatter and Markdown of the page in `source_lang` over its existing `target_lang` translation. The target translation file must already exist (create it with `POST /pages/{route}/translate`). The same Twig-content and validation checks as a normal page save apply. Needs multi-language enabled. A language code that is not a string is a 422. Requires `api.pages.write` and update access to the page.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"source_lang\": \"en\",\n  \"target_lang\": \"fr\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "id": "openapi:adoptPageLanguage",
          "name": "Assign an untyped page file to a language",
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "// Generated from openapi.yaml by tests/newman/sync-collection.php; the Newman suite skips it.",
                  "if (pm.variables.get('newman_suite')) { pm.execution.skipRequest(); }"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "X-API-Key",
                "value": "{{api_key}}",
                "description": "API Key authentication"
              },
              {
                "key": "X-Grav-Environment",
                "value": "{{grav_environment}}",
                "description": "Grav environment"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url}}{{api_prefix}}/pages/:route/adopt-language",
              "host": [
                "{{base_url}}{{api_prefix}}"
              ],
              "path": [
                "pages",
                ":route",
                "adopt-language"
              ],
              "variable": [
                {
                  "key": "route",
                  "value": "{{page_route}}",
                  "description": "The page route without leading slash (e.g. `blog/my-post`). Supports\nnested routes with `/` separators.\n"
                }
              ]
            },
            "description": "Rename a page's language-less file (for example `default.md`) to the language-specific name (`default.fr.md`) without touching its contents. Meant for sites that started single-language and later enabled multi-language. Fails with 422 when multi-language is off, when there is no language-less file, or when a file for that language already exists. Requires `api.pages.write` and update access to the page.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"lang\": \"en\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "id": "openapi:createPagePreviewToken",
          "name": "Get a draft preview token",
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "// Generated from openapi.yaml by tests/newman/sync-collection.php; the Newman suite skips it.",
                  "if (pm.variables.get('newman_suite')) { pm.execution.skipRequest(); }"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "X-API-Key",
                "value": "{{api_key}}",
                "description": "API Key authentication"
              },
              {
                "key": "X-Grav-Environment",
                "value": "{{grav_environment}}",
                "description": "Grav environment"
              }
            ],
            "url": {
              "raw": "{{base_url}}{{api_prefix}}/pages/:route/preview-token",
              "host": [
                "{{base_url}}{{api_prefix}}"
              ],
              "path": [
                "pages",
                ":route",
                "preview-token"
              ],
              "query": [
                {
                  "key": "lang",
                  "value": "",
                  "description": "Two-letter language code. When provided, the request operates on\nthe page translation for that language. If omitted, the default\nsite language is used.\n",
                  "disabled": true
                }
              ],
              "variable": [
                {
                  "key": "route",
                  "value": "{{page_route}}",
                  "description": "The page route without leading slash (e.g. `blog/my-post`). Supports\nnested routes with `/` separators.\n"
                }
              ]
            },
            "description": "Mint a short-lived signed token that lets the frontend render this page even when it is unpublished. Append it to the page's frontend URL as `?admin_preview=1&preview_token=<token>`; the token unlocks only this page. For a modular section, `route` is the parent page to load and `anchor` suggests where to scroll. Lifetime is `plugins.api.preview_token_ttl` seconds (default 300, minimum 30). Returns 403 when `plugins.api.allow_draft_preview` is off. Requires `api.pages.read` and read access to the page (and to its parent for a module)."
          }
        }
      ]
    },
    {
      "name": "Media",
      "description": "Media management — upload, list, and delete media files for pages and the site.",
      "item": [
        {
          "name": "List Page Media",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "const res = pm.response.json();",
                  "pm.test('Status is 200', () => pm.response.to.have.status(200));",
                  "pm.test('Has data array', () => pm.expect(res.data).to.be.an('array'));"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "url": {
              "raw": "{{base_url}}{{api_prefix}}/pages/blog/media",
              "host": [
                "{{base_url}}{{api_prefix}}"
              ],
              "path": [
                "pages",
                "blog",
                "media"
              ]
            },
            "description": "List all media files attached to a specific page.",
            "header": [
              {
                "key": "X-API-Key",
                "value": "{{api_key}}",
                "description": "API Key authentication"
              },
              {
                "key": "X-Grav-Environment",
                "value": "{{grav_environment}}",
                "description": "Grav environment"
              }
            ]
          }
        },
        {
          "name": "Upload Page Media",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('Status is 201', () => pm.response.to.have.status(201));"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "body": {
              "mode": "formdata",
              "formdata": [
                {
                  "key": "file",
                  "type": "file",
                  "src": "",
                  "description": "Select a file to upload"
                }
              ]
            },
            "url": {
              "raw": "{{base_url}}{{api_prefix}}/pages/blog/media",
              "host": [
                "{{base_url}}{{api_prefix}}"
              ],
              "path": [
                "pages",
                "blog",
                "media"
              ]
            },
            "description": "Upload one or more media files to a page. Use multipart/form-data. Files are validated against Grav's security config for dangerous extensions.",
            "header": [
              {
                "key": "X-API-Key",
                "value": "{{api_key}}",
                "description": "API Key authentication"
              },
              {
                "key": "X-Grav-Environment",
                "value": "{{grav_environment}}",
                "description": "Grav environment"
              }
            ]
          }
        },
        {
          "name": "Delete Page Media",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('Status is 204', () => pm.response.to.have.status(204));"
                ]
              }
            }
          ],
          "request": {
            "method": "DELETE",
            "url": {
              "raw": "{{base_url}}{{api_prefix}}/pages/blog/media/photo.jpg",
              "host": [
                "{{base_url}}{{api_prefix}}"
              ],
              "path": [
                "pages",
                "blog",
                "media",
                "photo.jpg"
              ]
            },
            "description": "Delete a specific media file from a page.",
            "header": [
              {
                "key": "X-API-Key",
                "value": "{{api_key}}",
                "description": "API Key authentication"
              },
              {
                "key": "X-Grav-Environment",
                "value": "{{grav_environment}}",
                "description": "Grav environment"
              }
            ]
          }
        },
        {
          "name": "List Site Media",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "const res = pm.response.json();",
                  "pm.test('Status is 200', () => pm.response.to.have.status(200));",
                  "pm.test('Has data array', () => pm.expect(res.data).to.be.an('array'));"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "url": {
              "raw": "{{base_url}}{{api_prefix}}/media?page=1&per_page=20",
              "host": [
                "{{base_url}}{{api_prefix}}"
              ],
              "path": [
                "media"
              ],
              "query": [
                {
                  "key": "page",
                  "value": "1"
                },
                {
                  "key": "per_page",
                  "value": "20"
                },
                {
                  "key": "path",
                  "value": "",
                  "description": "Subfolder path relative to user/media",
                  "disabled": true
                },
                {
                  "key": "search",
                  "value": "",
                  "description": "Recursive filename search across all subfolders",
                  "disabled": true
                },
                {
                  "key": "type",
                  "value": "image",
                  "description": "Filter by type: image, video, audio, document",
                  "disabled": true
                }
              ]
            },
            "description": "List site-level media files and folders from user/media. Supports subfolder browsing via path, recursive search, and type filtering. Response includes folders array in meta.",
            "header": [
              {
                "key": "X-API-Key",
                "value": "{{api_key}}",
                "description": "API Key authentication"
              },
              {
                "key": "X-Grav-Environment",
                "value": "{{grav_environment}}",
                "description": "Grav environment"
              }
            ]
          }
        },
        {
          "name": "Upload Site Media",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('Status is 201', () => pm.response.to.have.status(201));"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "body": {
              "mode": "formdata",
              "formdata": [
                {
                  "key": "file",
                  "type": "file",
                  "src": "",
                  "description": "Select a file to upload"
                }
              ]
            },
            "url": {
              "raw": "{{base_url}}{{api_prefix}}/media",
              "host": [
                "{{base_url}}{{api_prefix}}"
              ],
              "path": [
                "media"
              ],
              "query": [
                {
                  "key": "path",
                  "value": "",
                  "description": "Subfolder path to upload into (created automatically)",
                  "disabled": true
                }
              ]
            },
            "description": "Upload media files to the site-level media folder (user/media). Optionally specify a subfolder path.",
            "header": [
              {
                "key": "X-API-Key",
                "value": "{{api_key}}",
                "description": "API Key authentication"
              },
              {
                "key": "X-Grav-Environment",
                "value": "{{grav_environment}}",
                "description": "Grav environment"
              }
            ]
          }
        },
        {
          "name": "Delete Site Media",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('Status is 204', () => pm.response.to.have.status(204));"
                ]
              }
            }
          ],
          "request": {
            "method": "DELETE",
            "url": {
              "raw": "{{base_url}}{{api_prefix}}/media/logo.png",
              "host": [
                "{{base_url}}{{api_prefix}}"
              ],
              "path": [
                "media",
                "logo.png"
              ]
            },
            "description": "Delete a site-level media file. Filename may include a subfolder path (e.g. blog/hero.jpg).",
            "header": [
              {
                "key": "X-API-Key",
                "value": "{{api_key}}",
                "description": "API Key authentication"
              },
              {
                "key": "X-Grav-Environment",
                "value": "{{grav_environment}}",
                "description": "Grav environment"
              }
            ]
          }
        },
        {
          "name": "Create Media Folder",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('Status is 201', () => pm.response.to.have.status(201));"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"path\": \"blog/2026\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "url": {
              "raw": "{{base_url}}{{api_prefix}}/media/folders",
              "host": [
                "{{base_url}}{{api_prefix}}"
              ],
              "path": [
                "media",
                "folders"
              ]
            },
            "description": "Create a new folder in the site-level media directory.",
            "header": [
              {
                "key": "X-API-Key",
                "value": "{{api_key}}",
                "description": "API Key authentication"
              },
              {
                "key": "X-Grav-Environment",
                "value": "{{grav_environment}}",
                "description": "Grav environment"
              }
            ]
          }
        },
        {
          "name": "Delete Media Folder",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('Status is 204', () => pm.response.to.have.status(204));"
                ]
              }
            }
          ],
          "request": {
            "method": "DELETE",
            "url": {
              "raw": "{{base_url}}{{api_prefix}}/media/folders/blog/2026",
              "host": [
                "{{base_url}}{{api_prefix}}"
              ],
              "path": [
                "media",
                "folders",
                "blog",
                "2026"
              ]
            },
            "description": "Delete an empty folder from the site-level media directory.",
            "header": [
              {
                "key": "X-API-Key",
                "value": "{{api_key}}",
                "description": "API Key authentication"
              },
              {
                "key": "X-Grav-Environment",
                "value": "{{grav_environment}}",
                "description": "Grav environment"
              }
            ]
          }
        },
        {
          "name": "Rename Site Media",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('Status is 200', () => pm.response.to.have.status(200));"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"from\": \"blog/hero.jpg\",\n  \"to\": \"blog/banner.jpg\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "url": {
              "raw": "{{base_url}}{{api_prefix}}/media/rename",
              "host": [
                "{{base_url}}{{api_prefix}}"
              ],
              "path": [
                "media",
                "rename"
              ]
            },
            "description": "Rename or move a media file within the site-level media directory.",
            "header": [
              {
                "key": "X-API-Key",
                "value": "{{api_key}}",
                "description": "API Key authentication"
              },
              {
                "key": "X-Grav-Environment",
                "value": "{{grav_environment}}",
                "description": "Grav environment"
              }
            ]
          }
        },
        {
          "name": "Rename Media Folder",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('Status is 200', () => pm.response.to.have.status(200));"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"from\": \"blog/old-name\",\n  \"to\": \"blog/new-name\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "url": {
              "raw": "{{base_url}}{{api_prefix}}/media/folders/rename",
              "host": [
                "{{base_url}}{{api_prefix}}"
              ],
              "path": [
                "media",
                "folders",
                "rename"
              ]
            },
            "description": "Rename a folder within the site-level media directory.",
            "header": [
              {
                "key": "X-API-Key",
                "value": "{{api_key}}",
                "description": "API Key authentication"
              },
              {
                "key": "X-Grav-Environment",
                "value": "{{grav_environment}}",
                "description": "Grav environment"
              }
            ]
          }
        },
        {
          "name": "Get Page Media Metadata",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "const res = pm.response.json();",
                  "pm.test('Status is 200', () => pm.response.to.have.status(200));",
                  "pm.test('Has fields array', () => pm.expect(res.data.fields).to.be.an('array'));"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "url": {
              "raw": "{{base_url}}{{api_prefix}}/pages/blog/media/photo.jpg/meta",
              "host": [
                "{{base_url}}{{api_prefix}}"
              ],
              "path": [
                "pages",
                "blog",
                "media",
                "photo.jpg",
                "meta"
              ]
            },
            "description": "Read the editable metadata (alt, title, caption, description) for a page media file, plus read-only stored keys.",
            "header": [
              {
                "key": "X-API-Key",
                "value": "{{api_key}}",
                "description": "API Key authentication"
              },
              {
                "key": "X-Grav-Environment",
                "value": "{{grav_environment}}",
                "description": "Grav environment"
              }
            ]
          }
        },
        {
          "name": "Save Page Media Metadata",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "const res = pm.response.json();",
                  "pm.test('Status is 200', () => pm.response.to.have.status(200));",
                  "pm.test('Has fields array', () => pm.expect(res.data.fields).to.be.an('array'));"
                ]
              }
            }
          ],
          "request": {
            "method": "PATCH",
            "url": {
              "raw": "{{base_url}}{{api_prefix}}/pages/blog/media/photo.jpg/meta",
              "host": [
                "{{base_url}}{{api_prefix}}"
              ],
              "path": [
                "pages",
                "blog",
                "media",
                "photo.jpg",
                "meta"
              ]
            },
            "body": {
              "mode": "raw",
              "raw": "{\n  \"fields\": {\n    \"alt\": \"A dog running on the beach\",\n    \"title\": \"Beach Day\",\n    \"caption\": \"\",\n    \"description\": \"Our trip to the coast last summer.\",\n    \"tags\": [\n      \"beach\",\n      \"dog\",\n      \"summer\"\n    ]\n  }\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "description": "Write editable metadata for a page media file. Only configured fields are changed; other sidecar keys are preserved. Values are sanitized.",
            "header": [
              {
                "key": "X-API-Key",
                "value": "{{api_key}}",
                "description": "API Key authentication"
              },
              {
                "key": "X-Grav-Environment",
                "value": "{{grav_environment}}",
                "description": "Grav environment"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ]
          }
        },
        {
          "name": "Clear Page Media Metadata",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('Status is 204', () => pm.response.to.have.status(204));"
                ]
              }
            }
          ],
          "request": {
            "method": "DELETE",
            "url": {
              "raw": "{{base_url}}{{api_prefix}}/pages/blog/media/photo.jpg/meta",
              "host": [
                "{{base_url}}{{api_prefix}}"
              ],
              "path": [
                "pages",
                "blog",
                "media",
                "photo.jpg",
                "meta"
              ]
            },
            "description": "Clear all configured metadata fields for a page media file. Technical keys are preserved.",
            "header": [
              {
                "key": "X-API-Key",
                "value": "{{api_key}}",
                "description": "API Key authentication"
              },
              {
                "key": "X-Grav-Environment",
                "value": "{{grav_environment}}",
                "description": "Grav environment"
              }
            ]
          }
        },
        {
          "name": "Get Site Media Metadata",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "const res = pm.response.json();",
                  "pm.test('Status is 200', () => pm.response.to.have.status(200));",
                  "pm.test('Has fields array', () => pm.expect(res.data.fields).to.be.an('array'));"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "url": {
              "raw": "{{base_url}}{{api_prefix}}/media/meta?path=photo.jpg",
              "host": [
                "{{base_url}}{{api_prefix}}"
              ],
              "path": [
                "media",
                "meta"
              ],
              "query": [
                {
                  "key": "path",
                  "value": "photo.jpg",
                  "description": "File path relative to user/media"
                }
              ]
            },
            "description": "Read editable metadata for a site-level media file, addressed by ?path=.",
            "header": [
              {
                "key": "X-API-Key",
                "value": "{{api_key}}",
                "description": "API Key authentication"
              },
              {
                "key": "X-Grav-Environment",
                "value": "{{grav_environment}}",
                "description": "Grav environment"
              }
            ]
          }
        },
        {
          "name": "Save Site Media Metadata",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "const res = pm.response.json();",
                  "pm.test('Status is 200', () => pm.response.to.have.status(200));",
                  "pm.test('Has fields array', () => pm.expect(res.data.fields).to.be.an('array'));"
                ]
              }
            }
          ],
          "request": {
            "method": "PATCH",
            "url": {
              "raw": "{{base_url}}{{api_prefix}}/media/meta?path=photo.jpg",
              "host": [
                "{{base_url}}{{api_prefix}}"
              ],
              "path": [
                "media",
                "meta"
              ],
              "query": [
                {
                  "key": "path",
                  "value": "photo.jpg",
                  "description": "File path relative to user/media"
                }
              ]
            },
            "body": {
              "mode": "raw",
              "raw": "{\n  \"fields\": {\n    \"alt\": \"A dog running on the beach\",\n    \"title\": \"Beach Day\",\n    \"caption\": \"\",\n    \"description\": \"Our trip to the coast last summer.\",\n    \"tags\": [\n      \"beach\",\n      \"dog\",\n      \"summer\"\n    ]\n  }\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "description": "Write editable metadata for a site-level media file. Only configured fields are changed; other keys preserved.",
            "header": [
              {
                "key": "X-API-Key",
                "value": "{{api_key}}",
                "description": "API Key authentication"
              },
              {
                "key": "X-Grav-Environment",
                "value": "{{grav_environment}}",
                "description": "Grav environment"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ]
          }
        },
        {
          "name": "Clear Site Media Metadata",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('Status is 204', () => pm.response.to.have.status(204));"
                ]
              }
            }
          ],
          "request": {
            "method": "DELETE",
            "url": {
              "raw": "{{base_url}}{{api_prefix}}/media/meta?path=photo.jpg",
              "host": [
                "{{base_url}}{{api_prefix}}"
              ],
              "path": [
                "media",
                "meta"
              ],
              "query": [
                {
                  "key": "path",
                  "value": "photo.jpg",
                  "description": "File path relative to user/media"
                }
              ]
            },
            "description": "Clear all configured metadata fields for a site-level media file.",
            "header": [
              {
                "key": "X-API-Key",
                "value": "{{api_key}}",
                "description": "API Key authentication"
              },
              {
                "key": "X-Grav-Environment",
                "value": "{{grav_environment}}",
                "description": "Grav environment"
              }
            ]
          }
        },
        {
          "id": "openapi:deleteMediaFolder",
          "name": "Delete an empty media folder",
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "// Generated from openapi.yaml by tests/newman/sync-collection.php; the Newman suite skips it.",
                  "if (pm.variables.get('newman_suite')) { pm.execution.skipRequest(); }"
                ]
              }
            }
          ],
          "request": {
            "method": "DELETE",
            "header": [
              {
                "key": "X-API-Key",
                "value": "{{api_key}}",
                "description": "API Key authentication"
              },
              {
                "key": "X-Grav-Environment",
                "value": "{{grav_environment}}",
                "description": "Grav environment"
              }
            ],
            "url": {
              "raw": "{{base_url}}{{api_prefix}}/media/folders/:path",
              "host": [
                "{{base_url}}{{api_prefix}}"
              ],
              "path": [
                "media",
                "folders",
                ":path"
              ],
              "variable": [
                {
                  "key": "path",
                  "value": "",
                  "description": "Folder path relative to the media root."
                }
              ]
            },
            "description": "Remove an empty folder from the site-level media directory. The folder\nmust be empty (contain no files or subdirectories, hidden files and\nsidecars included); otherwise 422. Requires `api.media.write`."
          }
        },
        {
          "id": "openapi:getRawSiteMedia",
          "name": "Download a site media file",
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "// Generated from openapi.yaml by tests/newman/sync-collection.php; the Newman suite skips it.",
                  "if (pm.variables.get('newman_suite')) { pm.execution.skipRequest(); }"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "X-API-Key",
                "value": "{{api_key}}",
                "description": "API Key authentication"
              },
              {
                "key": "X-Grav-Environment",
                "value": "{{grav_environment}}",
                "description": "Grav environment"
              }
            ],
            "url": {
              "raw": "{{base_url}}{{api_prefix}}/media/raw/:path",
              "host": [
                "{{base_url}}{{api_prefix}}"
              ],
              "path": [
                "media",
                "raw",
                ":path"
              ],
              "variable": [
                {
                  "key": "path",
                  "value": "",
                  "description": "Path relative to the site media root. May contain `/`."
                }
              ]
            },
            "description": "Stream a file from the site media folder. Used for files the web server will not serve directly; everything else keeps its static URL. Only images, video, audio, fonts and PDFs are served; any other type returns 404 so the route cannot be used to read source files. Responses carry a sandboxing `Content-Security-Policy` so an uploaded SVG cannot run script when opened directly. Works as an `<img src>` because the API accepts the site's session cookie. Requires `api.media.read`."
          }
        },
        {
          "id": "openapi:batchUpdateSiteMediaMeta",
          "name": "Apply metadata to several site media files",
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "// Generated from openapi.yaml by tests/newman/sync-collection.php; the Newman suite skips it.",
                  "if (pm.variables.get('newman_suite')) { pm.execution.skipRequest(); }"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "X-API-Key",
                "value": "{{api_key}}",
                "description": "API Key authentication"
              },
              {
                "key": "X-Grav-Environment",
                "value": "{{grav_environment}}",
                "description": "Grav environment"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url}}{{api_prefix}}/media/batch/meta",
              "host": [
                "{{base_url}}{{api_prefix}}"
              ],
              "path": [
                "media",
                "batch",
                "meta"
              ]
            },
            "description": "Write the same metadata fields to many site media files at once. Only the configured fields present in `fields` change; each file keeps its other values. Every file is handled on its own, so one failure is reported in `results` without stopping the rest. The number of files is capped by `plugins.api.batch.max_items` (default 50). Requires `api.media.write`.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"files\": [\n    \"photos/beach.jpg\",\n    \"photos/dunes.jpg\"\n  ],\n  \"fields\": {\n    \"tags\": [\n      \"beach\",\n      \"summer\"\n    ]\n  }\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "id": "openapi:setSiteMediaOrder",
          "name": "Save the manual file order of a media folder",
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "// Generated from openapi.yaml by tests/newman/sync-collection.php; the Newman suite skips it.",
                  "if (pm.variables.get('newman_suite')) { pm.execution.skipRequest(); }"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "X-API-Key",
                "value": "{{api_key}}",
                "description": "API Key authentication"
              },
              {
                "key": "X-Grav-Environment",
                "value": "{{grav_environment}}",
                "description": "Grav environment"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url}}{{api_prefix}}/media/order",
              "host": [
                "{{base_url}}{{api_prefix}}"
              ],
              "path": [
                "media",
                "order"
              ]
            },
            "description": "Store a manual ordering for the files in a site media folder. The order is written to a `media_order.yaml` file in that folder and applied when the folder is listed. Entries are reduced to base filenames. Requires `api.media.write`.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"path\": \"photos\",\n  \"order\": [\n    \"dunes.jpg\",\n    \"beach.jpg\",\n    \"sunset.jpg\"\n  ]\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "id": "openapi:uploadBlueprintFile",
          "name": "Upload a file for a blueprint file field",
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "// Generated from openapi.yaml by tests/newman/sync-collection.php; the Newman suite skips it.",
                  "if (pm.variables.get('newman_suite')) { pm.execution.skipRequest(); }"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "X-API-Key",
                "value": "{{api_key}}",
                "description": "API Key authentication"
              },
              {
                "key": "X-Grav-Environment",
                "value": "{{grav_environment}}",
                "description": "Grav environment"
              }
            ],
            "url": {
              "raw": "{{base_url}}{{api_prefix}}/blueprint-upload",
              "host": [
                "{{base_url}}{{api_prefix}}"
              ],
              "path": [
                "blueprint-upload"
              ]
            },
            "description": "Destination-aware upload for blueprint `type: file` fields (a theme logo, a plugin asset, an avatar), mirroring classic admin's `taskFilesUpload`. Requires `api.media.write`.\nThe target folder comes from the field's `destination`: a Grav stream (`theme://images`, `user://media`), a `self@:subpath` token resolved against `scope`, or a plain path relative to `user/`. Absolute paths and `..` segments are rejected.\nEvery multipart file part is saved (nested file arrays are flattened). Each file is capped at 64 MB, and the field's own `filesize` can only tighten that. Extensions listed in `security.uploads_dangerous_extensions` are refused in any dot-separated part of the name (so `shell.php.jpg` fails), as are config-type extensions (`yaml`, `yml`, `json`, `twig`, `env`, `neon`, `lock`), page content (`md`, `markdown`) and stylesheets (`css`, `scss`, `sass`, `less`). Uploads into `user/accounts/` are restricted to image extensions, and uploads resolving into `user/config/` or `user/env/*/config/` are refused with 403. SVG files are sanitized after saving.\nA developer can allow page content or stylesheets for one field by listing them in that field's blueprint, for example `allow_extensions: [css]`. The server reads this from the blueprint that owns `scope` (a plugin or theme config blueprint, the page template's blueprint, or the account blueprint), never from the request: the request only names the field in `field`, and the upload's `destination` must be that field's own `destination`. `allow_extensions` can only lift `md`, `markdown`, `css`, `scss`, `sass` and `less`; dangerous and config-type extensions, the accounts image-only rule and the config directory block always apply.\n`self@:` against a `users/<username>` scope for another account needs `api.users.write` or super-admin.",
            "body": {
              "mode": "formdata",
              "formdata": [
                {
                  "key": "file",
                  "type": "file",
                  "src": [],
                  "description": "File to upload. Any field name works, and several file parts may be sent in one request."
                },
                {
                  "key": "destination",
                  "type": "text",
                  "value": "",
                  "description": "The blueprint field's `destination` value."
                },
                {
                  "key": "scope",
                  "type": "text",
                  "value": "",
                  "description": "Owner of the blueprint, used to resolve `self@:` destinations. One of `plugins/<slug>`, `themes/<slug>`, `pages/<route>`, `users/<username>`.\n"
                },
                {
                  "key": "field",
                  "type": "text",
                  "value": "",
                  "description": "Full name of the blueprint field the upload belongs to, as the form knows it (`custom_css`, `header.stylesheet`). Used together with `scope` to look up the field's `allow_extensions` in the blueprint on the server. Without it, or without `scope`, page content and stylesheets are refused.\n"
                },
                {
                  "key": "random_name",
                  "type": "text",
                  "value": "",
                  "description": "Replace the file name with a random one (extension kept)."
                },
                {
                  "key": "avoid_overwriting",
                  "type": "text",
                  "value": "",
                  "description": "Add a suffix instead of replacing an existing file of the same name."
                },
                {
                  "key": "accept",
                  "type": "text",
                  "value": "",
                  "description": "Comma-separated allowlist of extensions (`.pdf`, `*.jpg`) or MIME patterns (`image/*`). May also be sent as an array."
                },
                {
                  "key": "filesize",
                  "type": "text",
                  "value": "",
                  "description": "Per-field maximum size in MB."
                }
              ]
            }
          }
        },
        {
          "id": "openapi:deleteBlueprintFile",
          "name": "Delete a file saved by a blueprint file field",
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "// Generated from openapi.yaml by tests/newman/sync-collection.php; the Newman suite skips it.",
                  "if (pm.variables.get('newman_suite')) { pm.execution.skipRequest(); }"
                ]
              }
            }
          ],
          "request": {
            "method": "DELETE",
            "header": [
              {
                "key": "X-API-Key",
                "value": "{{api_key}}",
                "description": "API Key authentication"
              },
              {
                "key": "X-Grav-Environment",
                "value": "{{grav_environment}}",
                "description": "Grav environment"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url}}{{api_prefix}}/blueprint-upload",
              "host": [
                "{{base_url}}{{api_prefix}}"
              ],
              "path": [
                "blueprint-upload"
              ]
            },
            "description": "Remove a file previously saved through `POST /blueprint-upload`, plus its `.meta.yaml` sidecar when present. Requires `api.media.write`.\n`path` is the logical path the upload returned, relative to the user root (`user/` prefix optional). Absolute input, `..` and null bytes are rejected. Targets in `user/config/` or `user/env/*/config/` are refused with 403. Under `user/accounts/` only image files may be deleted, and without `api.users.write` only the caller's own avatar (403 otherwise). The same extension denylist as the upload applies, so page content (`.md`) and stylesheets can't be deleted here unless `field` and `scope` name a blueprint field that allows them with `allow_extensions` and the file sits in the folder that field's `destination` resolves to.\nThe call is idempotent: a file that no longer exists returns 204.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"path\": \"user/themes/quark/images/logo/logo.png\",\n  \"field\": \"custom_css\",\n  \"scope\": \"themes/quark\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "id": "openapi:listBlueprintFiles",
          "name": "List files for a blueprint folder field",
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "// Generated from openapi.yaml by tests/newman/sync-collection.php; the Newman suite skips it.",
                  "if (pm.variables.get('newman_suite')) { pm.execution.skipRequest(); }"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "X-API-Key",
                "value": "{{api_key}}",
                "description": "API Key authentication"
              },
              {
                "key": "X-Grav-Environment",
                "value": "{{grav_environment}}",
                "description": "Grav environment"
              }
            ],
            "url": {
              "raw": "{{base_url}}{{api_prefix}}/blueprint-files?folder=",
              "host": [
                "{{base_url}}{{api_prefix}}"
              ],
              "path": [
                "blueprint-files"
              ],
              "query": [
                {
                  "key": "folder",
                  "value": "",
                  "description": "Folder to list (stream, `self@:` token, or path relative to `user/`).",
                  "disabled": false
                },
                {
                  "key": "scope",
                  "value": "",
                  "description": "Blueprint owner used to resolve `self@:` (`plugins/<slug>`, `themes/<slug>`, `pages/<route>`, `users/<username>`).",
                  "disabled": true
                },
                {
                  "key": "accept",
                  "value": "",
                  "description": "Comma-separated filter of extensions (`.pdf`, `*.jpg`) or MIME patterns (`image/*`). `*` matches everything.",
                  "disabled": true
                }
              ]
            },
            "description": "Read-only file listing for blueprint fields that declare a `folder:` option (filepicker, mediapicker), mirroring classic admin's `taskGetFilesInFolder`. Requires `api.media.read`.\n`folder` may be a Grav stream (`user://media`, `theme://images`, `account://`), a `self@:subpath` token resolved against `scope`, or a plain path relative to `user/`. A folder that does not exist yet returns an empty list with `meta.exists: false`.\nThe page-media literals `@self` / `self@` (with no subpath) are not handled here: the endpoint returns a 422 validation error whose `errors` entry for `folder` carries the message `PAGE_MEDIA_ONLY`, telling the client to use `GET /pages/{route}/media` instead.\nThe result uses the paginated envelope but is never actually paginated: every matching file is returned on page 1."
          }
        }
      ]
    },
    {
      "name": "Config",
      "description": "Configuration management — read and update system, site, plugin, and theme configs.",
      "item": [
        {
          "name": "Get System Config",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "const res = pm.response.json();",
                  "pm.test('Status is 200', () => pm.response.to.have.status(200));",
                  "pm.test('Has data', () => pm.expect(res.data).to.be.an('object'));",
                  "pm.test('Has ETag', () => pm.response.to.have.header('ETag'));"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "url": {
              "raw": "{{base_url}}{{api_prefix}}/config/system",
              "host": [
                "{{base_url}}{{api_prefix}}"
              ],
              "path": [
                "config",
                "system"
              ]
            },
            "description": "Read the system configuration.",
            "header": [
              {
                "key": "X-API-Key",
                "value": "{{api_key}}",
                "description": "API Key authentication"
              },
              {
                "key": "X-Grav-Environment",
                "value": "{{grav_environment}}",
                "description": "Grav environment"
              }
            ]
          }
        },
        {
          "name": "Get Site Config",
          "request": {
            "method": "GET",
            "url": {
              "raw": "{{base_url}}{{api_prefix}}/config/site",
              "host": [
                "{{base_url}}{{api_prefix}}"
              ],
              "path": [
                "config",
                "site"
              ]
            },
            "description": "Read the site configuration (site.yaml).",
            "header": [
              {
                "key": "X-API-Key",
                "value": "{{api_key}}",
                "description": "API Key authentication"
              },
              {
                "key": "X-Grav-Environment",
                "value": "{{grav_environment}}",
                "description": "Grav environment"
              }
            ]
          }
        },
        {
          "name": "Get Plugin Config",
          "request": {
            "method": "GET",
            "url": {
              "raw": "{{base_url}}{{api_prefix}}/config/plugins/markdown",
              "host": [
                "{{base_url}}{{api_prefix}}"
              ],
              "path": [
                "config",
                "plugins",
                "markdown"
              ]
            },
            "description": "Read configuration for a specific plugin. Change `markdown` to any plugin name.",
            "header": [
              {
                "key": "X-API-Key",
                "value": "{{api_key}}",
                "description": "API Key authentication"
              },
              {
                "key": "X-Grav-Environment",
                "value": "{{grav_environment}}",
                "description": "Grav environment"
              }
            ]
          }
        },
        {
          "name": "Get Theme Config",
          "request": {
            "method": "GET",
            "url": {
              "raw": "{{base_url}}{{api_prefix}}/config/themes/quark",
              "host": [
                "{{base_url}}{{api_prefix}}"
              ],
              "path": [
                "config",
                "themes",
                "quark"
              ]
            },
            "description": "Read configuration for a specific theme.",
            "header": [
              {
                "key": "X-API-Key",
                "value": "{{api_key}}",
                "description": "API Key authentication"
              },
              {
                "key": "X-Grav-Environment",
                "value": "{{grav_environment}}",
                "description": "Grav environment"
              }
            ]
          }
        },
        {
          "name": "Update Site Config",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "const res = pm.response.json();",
                  "pm.test('Status is 200', () => pm.response.to.have.status(200));",
                  "pm.test('Has updated data', () => pm.expect(res.data).to.be.an('object'));"
                ]
              }
            }
          ],
          "request": {
            "method": "PATCH",
            "header": [
              {
                "key": "X-API-Key",
                "value": "{{api_key}}",
                "description": "API Key authentication"
              },
              {
                "key": "X-Grav-Environment",
                "value": "{{grav_environment}}",
                "description": "Grav environment"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "If-Match",
                "value": "\"etag-from-get\"",
                "disabled": true
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n    \"title\": \"My Updated Site Title\",\n    \"metadata\": {\n        \"description\": \"Updated via the Grav API\"\n    }\n}"
            },
            "url": {
              "raw": "{{base_url}}{{api_prefix}}/config/site",
              "host": [
                "{{base_url}}{{api_prefix}}"
              ],
              "path": [
                "config",
                "site"
              ]
            },
            "description": "Update site configuration. Values are deep-merged with existing config. Only send the fields you want to change."
          }
        },
        {
          "name": "Update Plugin Config",
          "request": {
            "method": "PATCH",
            "header": [
              {
                "key": "X-API-Key",
                "value": "{{api_key}}",
                "description": "API Key authentication"
              },
              {
                "key": "X-Grav-Environment",
                "value": "{{grav_environment}}",
                "description": "Grav environment"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n    \"extra\": true,\n    \"auto_line_breaks\": false\n}"
            },
            "url": {
              "raw": "{{base_url}}{{api_prefix}}/config/plugins/markdown",
              "host": [
                "{{base_url}}{{api_prefix}}"
              ],
              "path": [
                "config",
                "plugins",
                "markdown"
              ]
            },
            "description": "Update a plugin's configuration."
          }
        },
        {
          "id": "openapi:listConfigScopes",
          "name": "List configuration sections",
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "// Generated from openapi.yaml by tests/newman/sync-collection.php; the Newman suite skips it.",
                  "if (pm.variables.get('newman_suite')) { pm.execution.skipRequest(); }"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "X-API-Key",
                "value": "{{api_key}}",
                "description": "API Key authentication"
              },
              {
                "key": "X-Grav-Environment",
                "value": "{{grav_environment}}",
                "description": "Grav environment"
              }
            ],
            "url": {
              "raw": "{{base_url}}{{api_prefix}}/config",
              "host": [
                "{{base_url}}{{api_prefix}}"
              ],
              "path": [
                "config"
              ]
            },
            "description": "Returns the configuration scopes the admin shows as tabs. Requires `api.config.read`.\nThe list always starts with `system` and `site` and ends with `info` (a client-side system-information tab, not a readable scope). In between, sorted by name, come the core scopes `media` and `security` plus any site- or extension-authored custom scopes. `backups` is included for super-admins only. `scheduler` is never listed."
          }
        },
        {
          "id": "openapi:getAccountsConfig",
          "name": "Read the accounts configuration",
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "// Generated from openapi.yaml by tests/newman/sync-collection.php; the Newman suite skips it.",
                  "if (pm.variables.get('newman_suite')) { pm.execution.skipRequest(); }"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "X-API-Key",
                "value": "{{api_key}}",
                "description": "API Key authentication"
              },
              {
                "key": "X-Grav-Environment",
                "value": "{{grav_environment}}",
                "description": "Grav environment"
              }
            ],
            "url": {
              "raw": "{{base_url}}{{api_prefix}}/config/accounts",
              "host": [
                "{{base_url}}{{api_prefix}}"
              ],
              "path": [
                "config",
                "accounts"
              ]
            },
            "description": "Returns the Flex accounts configuration (`flex.accounts`, stored in `user/config/flex/accounts.yaml`), the Users \"Configuration\" tab in classic admin. Requires super-admin (`admin.super`). The response includes an `ETag` for use with `If-Match` on PATCH."
          }
        },
        {
          "id": "openapi:updateAccountsConfig",
          "name": "Update the accounts configuration",
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "// Generated from openapi.yaml by tests/newman/sync-collection.php; the Newman suite skips it.",
                  "if (pm.variables.get('newman_suite')) { pm.execution.skipRequest(); }"
                ]
              }
            }
          ],
          "request": {
            "method": "PATCH",
            "header": [
              {
                "key": "X-API-Key",
                "value": "{{api_key}}",
                "description": "API Key authentication"
              },
              {
                "key": "X-Grav-Environment",
                "value": "{{grav_environment}}",
                "description": "Grav environment"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url}}{{api_prefix}}/config/accounts",
              "host": [
                "{{base_url}}{{api_prefix}}"
              ],
              "path": [
                "config",
                "accounts"
              ]
            },
            "description": "Deep-merges the body into the accounts configuration and writes it to the base `user/config/flex/accounts.yaml` (never an environment overlay). Nested maps merge; lists are replaced whole. Values injected through `GRAV_CONFIG__*` environment variables are not written to disk. Requires super-admin (`admin.super`).\nSend the current `ETag` in `If-Match` to detect concurrent edits. Fires `onApiConfigUpdated` with scope `flex/accounts`.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"pages\": {\n    \"theme\": \"quark\"\n  },\n  \"debugger\": {\n    \"enabled\": false\n  }\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "id": "openapi:revertConfig",
          "name": "Revert configuration overrides",
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "// Generated from openapi.yaml by tests/newman/sync-collection.php; the Newman suite skips it.",
                  "if (pm.variables.get('newman_suite')) { pm.execution.skipRequest(); }"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "X-API-Key",
                "value": "{{api_key}}",
                "description": "API Key authentication"
              },
              {
                "key": "X-Grav-Environment",
                "value": "{{grav_environment}}",
                "description": "Grav environment"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url}}{{api_prefix}}/config/:scope/revert",
              "host": [
                "{{base_url}}{{api_prefix}}"
              ],
              "path": [
                "config",
                ":scope",
                "revert"
              ],
              "variable": [
                {
                  "key": "scope",
                  "value": "",
                  "description": "Configuration scope. One of: `system`, `site`, `media`, `security`,\n`scheduler`, `backups`, `plugins/{name}`, `themes/{name}`, or the\nname of a site-authored `user/config/<name>.yaml` file. The slash in\n`plugins/{name}` and `themes/{name}` is sent unencoded.\n"
                }
              ]
            },
            "description": "Drops overrides from the active configuration layer so the values fall back to the parent layer (defaults for a base write, defaults plus base for an environment write). Requires `api.config.write`. Writes to `system`, `security` and `plugins/api` also need an API super user, and the tool-managed `scheduler` and `backups` scopes are super-only for reading and writing.\nSend `keys` (dotted paths) to remove individual overrides, or `reset: true` to delete the layer's file for that scope entirely. To remove an environment value listed in `meta.shadowed`, revert it with `X-Config-Environment` set to that environment; `If-Match` is optional, so this needs no prior read of the environment. A file left empty is deleted, but the environment folder stays. The target layer follows the same rules as `PATCH /config/{scope}`: the `X-Config-Environment` header picks it, an empty value (or `default` / `base`) means the base `user/config`, and when absent the active environment is used if it has a config folder.\nThe response is the effective configuration after the revert, with secrets masked, and `meta` lists the paths that still override the parent layer. Reverting something the layer does not override (for example `reset: true` when the layer has no file) is still a 200, but `meta.reverted` is false and no `onApiConfigUpdated` event (so no `config.updated` webhook) fires.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"keys\": [\n    \"pages.theme\",\n    \"debugger.enabled\"\n  ],\n  \"reset\": false\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        }
      ]
    },
    {
      "name": "Users",
      "description": "User account management and API key administration.",
      "item": [
        {
          "name": "List Users",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "const res = pm.response.json();",
                  "pm.test('Status is 200', () => pm.response.to.have.status(200));",
                  "pm.test('Has data array', () => pm.expect(res.data).to.be.an('array'));",
                  "pm.test('Has pagination', () => pm.expect(res.meta.pagination).to.have.property('total'));"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "url": {
              "raw": "{{base_url}}{{api_prefix}}/users?page=1&per_page=20",
              "host": [
                "{{base_url}}{{api_prefix}}"
              ],
              "path": [
                "users"
              ],
              "query": [
                {
                  "key": "page",
                  "value": "1"
                },
                {
                  "key": "per_page",
                  "value": "20"
                }
              ]
            },
            "description": "List all user accounts (paginated). Sensitive fields (password, API key hashes) are excluded.",
            "header": [
              {
                "key": "X-API-Key",
                "value": "{{api_key}}",
                "description": "API Key authentication"
              },
              {
                "key": "X-Grav-Environment",
                "value": "{{grav_environment}}",
                "description": "Grav environment"
              }
            ]
          }
        },
        {
          "name": "Get User",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "const res = pm.response.json();",
                  "pm.test('Status is 200', () => pm.response.to.have.status(200));",
                  "pm.test('Has username', () => pm.expect(res.data).to.have.property('username'));",
                  "pm.test('No password field', () => pm.expect(res.data).to.not.have.property('hashed_password'));"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "url": {
              "raw": "{{base_url}}{{api_prefix}}/users/admin",
              "host": [
                "{{base_url}}{{api_prefix}}"
              ],
              "path": [
                "users",
                "admin"
              ]
            },
            "description": "Get details for a specific user.",
            "header": [
              {
                "key": "X-API-Key",
                "value": "{{api_key}}",
                "description": "API Key authentication"
              },
              {
                "key": "X-Grav-Environment",
                "value": "{{grav_environment}}",
                "description": "Grav environment"
              }
            ]
          }
        },
        {
          "name": "Create User",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('Status is 201', () => pm.response.to.have.status(201));"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "X-API-Key",
                "value": "{{api_key}}",
                "description": "API Key authentication"
              },
              {
                "key": "X-Grav-Environment",
                "value": "{{grav_environment}}",
                "description": "Grav environment"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n    \"username\": \"{{test_username}}\",\n    \"password\": \"SecureP@ssw0rd!\",\n    \"email\": \"testuser@example.com\",\n    \"fullname\": \"Test User\",\n    \"title\": \"Editor\",\n    \"access\": {\n        \"api\": {\n            \"access\": true,\n            \"pages\": {\n                \"read\": true,\n                \"write\": true\n            }\n        }\n    }\n}"
            },
            "url": {
              "raw": "{{base_url}}{{api_prefix}}/users",
              "host": [
                "{{base_url}}{{api_prefix}}"
              ],
              "path": [
                "users"
              ]
            },
            "description": "Create a new user account. Required: `username`, `password`, `email`."
          }
        },
        {
          "name": "Update User",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('Status is 200', () => pm.response.to.have.status(200));"
                ]
              }
            }
          ],
          "request": {
            "method": "PATCH",
            "header": [
              {
                "key": "X-API-Key",
                "value": "{{api_key}}",
                "description": "API Key authentication"
              },
              {
                "key": "X-Grav-Environment",
                "value": "{{grav_environment}}",
                "description": "Grav environment"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n    \"fullname\": \"Updated Test User\",\n    \"title\": \"Senior Editor\"\n}"
            },
            "url": {
              "raw": "{{base_url}}{{api_prefix}}/users/{{test_username}}",
              "host": [
                "{{base_url}}{{api_prefix}}"
              ],
              "path": [
                "users",
                "{{test_username}}"
              ]
            },
            "description": "Partial update a user. Only send the fields you want to change."
          }
        },
        {
          "name": "List API Keys",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "const res = pm.response.json();",
                  "pm.test('Status is 200', () => pm.response.to.have.status(200));",
                  "pm.test('Has data array', () => pm.expect(res.data).to.be.an('array'));"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "url": {
              "raw": "{{base_url}}{{api_prefix}}/users/{{test_username}}/api-keys",
              "host": [
                "{{base_url}}{{api_prefix}}"
              ],
              "path": [
                "users",
                "{{test_username}}",
                "api-keys"
              ]
            },
            "description": "List all API keys for a user. Returns metadata only (no hashes).",
            "header": [
              {
                "key": "X-API-Key",
                "value": "{{api_key}}",
                "description": "API Key authentication"
              },
              {
                "key": "X-Grav-Environment",
                "value": "{{grav_environment}}",
                "description": "Grav environment"
              }
            ]
          }
        },
        {
          "name": "Create API Key",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "const res = pm.response.json();",
                  "pm.test('Status is 201', () => pm.response.to.have.status(201));",
                  "pm.test('Has raw key', () => pm.expect(res.data).to.have.property('key'));",
                  "pm.test('Key starts with grav_', () => pm.expect(res.data.key).to.match(/^grav_/));",
                  "",
                  "if (res.data && res.data.id) {",
                  "    pm.collectionVariables.set('test_api_key_id', res.data.id);",
                  "}"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "X-API-Key",
                "value": "{{api_key}}",
                "description": "API Key authentication"
              },
              {
                "key": "X-Grav-Environment",
                "value": "{{grav_environment}}",
                "description": "Grav environment"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n    \"name\": \"Test CLI Key\",\n    \"scopes\": [\"api.pages.read\", \"api.pages.write\"],\n    \"expiry_days\": 90\n}"
            },
            "url": {
              "raw": "{{base_url}}{{api_prefix}}/users/{{test_username}}/api-keys",
              "host": [
                "{{base_url}}{{api_prefix}}"
              ],
              "path": [
                "users",
                "{{test_username}}",
                "api-keys"
              ]
            },
            "description": "Generate a new API key. The raw key is returned ONCE — save it immediately. The key ID is saved to the `test_api_key_id` variable for the revoke request."
          }
        },
        {
          "name": "Revoke API Key",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('Status is 204', () => pm.response.to.have.status(204));"
                ]
              }
            }
          ],
          "request": {
            "method": "DELETE",
            "url": {
              "raw": "{{base_url}}{{api_prefix}}/users/{{test_username}}/api-keys/{{test_api_key_id}}",
              "host": [
                "{{base_url}}{{api_prefix}}"
              ],
              "path": [
                "users",
                "{{test_username}}",
                "api-keys",
                "{{test_api_key_id}}"
              ]
            },
            "description": "Revoke (delete) a specific API key.",
            "header": [
              {
                "key": "X-API-Key",
                "value": "{{api_key}}",
                "description": "API Key authentication"
              },
              {
                "key": "X-Grav-Environment",
                "value": "{{grav_environment}}",
                "description": "Grav environment"
              }
            ]
          }
        },
        {
          "name": "Upload Avatar",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "X-API-Key",
                "value": "{{api_key}}"
              },
              {
                "key": "X-Grav-Environment",
                "value": "{{grav_environment}}"
              }
            ],
            "url": {
              "raw": "{{base_url}}{{api_prefix}}/users/{{test_username}}/avatar",
              "host": [
                "{{base_url}}{{api_prefix}}"
              ],
              "path": [
                "users",
                "{{test_username}}",
                "avatar"
              ]
            },
            "description": "Upload a custom avatar image for a user.",
            "body": {
              "mode": "formdata",
              "formdata": [
                {
                  "key": "avatar",
                  "type": "file",
                  "src": ""
                }
              ]
            }
          }
        },
        {
          "name": "Delete Avatar",
          "request": {
            "method": "DELETE",
            "header": [
              {
                "key": "X-API-Key",
                "value": "{{api_key}}"
              },
              {
                "key": "X-Grav-Environment",
                "value": "{{grav_environment}}"
              },
              {
                "key": "Accept",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url}}{{api_prefix}}/users/{{test_username}}/avatar",
              "host": [
                "{{base_url}}{{api_prefix}}"
              ],
              "path": [
                "users",
                "{{test_username}}",
                "avatar"
              ]
            },
            "description": "Remove the user's custom avatar."
          }
        },
        {
          "name": "Generate 2FA Secret",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "X-API-Key",
                "value": "{{api_key}}"
              },
              {
                "key": "X-Grav-Environment",
                "value": "{{grav_environment}}"
              },
              {
                "key": "Accept",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url}}{{api_prefix}}/users/{{test_username}}/2fa",
              "host": [
                "{{base_url}}{{api_prefix}}"
              ],
              "path": [
                "users",
                "{{test_username}}",
                "2fa"
              ]
            },
            "description": "Generate a new TOTP 2FA secret and return the QR code as a data URI."
          }
        },
        {
          "name": "Delete User",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('Status is 204', () => pm.response.to.have.status(204));"
                ]
              }
            }
          ],
          "request": {
            "method": "DELETE",
            "url": {
              "raw": "{{base_url}}{{api_prefix}}/users/{{test_username}}",
              "host": [
                "{{base_url}}{{api_prefix}}"
              ],
              "path": [
                "users",
                "{{test_username}}"
              ]
            },
            "description": "Delete a user account. You cannot delete yourself.",
            "header": [
              {
                "key": "X-API-Key",
                "value": "{{api_key}}",
                "description": "API Key authentication"
              },
              {
                "key": "X-Grav-Environment",
                "value": "{{grav_environment}}",
                "description": "Grav environment"
              }
            ]
          }
        },
        {
          "id": "openapi:listUserListColumns",
          "name": "List plugin columns for the users list",
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "// Generated from openapi.yaml by tests/newman/sync-collection.php; the Newman suite skips it.",
                  "if (pm.variables.get('newman_suite')) { pm.execution.skipRequest(); }"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "X-API-Key",
                "value": "{{api_key}}",
                "description": "API Key authentication"
              },
              {
                "key": "X-Grav-Environment",
                "value": "{{grav_environment}}",
                "description": "Grav environment"
              }
            ],
            "url": {
              "raw": "{{base_url}}{{api_prefix}}/users/columns",
              "host": [
                "{{base_url}}{{api_prefix}}"
              ],
              "path": [
                "users",
                "columns"
              ]
            },
            "description": "Return the extra columns plugins add to the users list through the `onApiUserListColumns` event, filtered by each column's `authorize` and sorted by priority. The values for each user arrive in that user's `extra` map on `GET /users`. Requires `api.users.read`."
          }
        },
        {
          "id": "openapi:listUserListFilters",
          "name": "List filter tabs for the users list",
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "// Generated from openapi.yaml by tests/newman/sync-collection.php; the Newman suite skips it.",
                  "if (pm.variables.get('newman_suite')) { pm.execution.skipRequest(); }"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "X-API-Key",
                "value": "{{api_key}}",
                "description": "API Key authentication"
              },
              {
                "key": "X-Grav-Environment",
                "value": "{{grav_environment}}",
                "description": "Grav environment"
              }
            ],
            "url": {
              "raw": "{{base_url}}{{api_prefix}}/users/filters",
              "host": [
                "{{base_url}}{{api_prefix}}"
              ],
              "path": [
                "users",
                "filters"
              ]
            },
            "description": "Return the tabs shown above the users list. Plugins add tabs through the `onApiUserListFilters` event; selecting one sends `?filter=<id>` to `GET /users`. The built-in `all` tab leads the row unless a plugin hides it (only possible once that plugin contributes a visible tab of its own). Requires `api.users.read`."
          }
        },
        {
          "id": "openapi:listUserRowActions",
          "name": "List plugin row actions for the users list",
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "// Generated from openapi.yaml by tests/newman/sync-collection.php; the Newman suite skips it.",
                  "if (pm.variables.get('newman_suite')) { pm.execution.skipRequest(); }"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "X-API-Key",
                "value": "{{api_key}}",
                "description": "API Key authentication"
              },
              {
                "key": "X-Grav-Environment",
                "value": "{{grav_environment}}",
                "description": "Grav environment"
              }
            ],
            "url": {
              "raw": "{{base_url}}{{api_prefix}}/users/row-actions",
              "host": [
                "{{base_url}}{{api_prefix}}"
              ],
              "path": [
                "users",
                "row-actions"
              ]
            },
            "description": "Return the per-user action buttons plugins declare through the `onApiUserListRowActions` event, filtered by each action's `authorize`, capped at 24 and sorted by priority. Run one with `POST /users/{username}/row-action`. Requires `api.users.read`."
          }
        },
        {
          "id": "openapi:runUserRowAction",
          "name": "Run a plugin row action on a user",
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "// Generated from openapi.yaml by tests/newman/sync-collection.php; the Newman suite skips it.",
                  "if (pm.variables.get('newman_suite')) { pm.execution.skipRequest(); }"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "X-API-Key",
                "value": "{{api_key}}",
                "description": "API Key authentication"
              },
              {
                "key": "X-Grav-Environment",
                "value": "{{grav_environment}}",
                "description": "Grav environment"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url}}{{api_prefix}}/users/:username/row-action",
              "host": [
                "{{base_url}}{{api_prefix}}"
              ],
              "path": [
                "users",
                ":username",
                "row-action"
              ],
              "variable": [
                {
                  "key": "username",
                  "value": "{{test_username}}",
                  "description": "The username of the user account."
                }
              ]
            },
            "description": "Run a declared row action against one user through the `onApiUserListRowAction` event. The action's `authorize` is checked again on the server, and an action the caller may not use returns 404 just like an unknown id. Non-super callers cannot target a super-admin account. The plugin handler applies its own per-target rules on top. The result is reduced to `status`, `message` and a same-origin `url`; a handler error comes back as 200 with `status: error` rather than failing the request. Requires `api.users.read`.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"id\": \"impersonate-user\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "id": "openapi:enableUser2FA",
          "name": "Turn on 2FA",
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "// Generated from openapi.yaml by tests/newman/sync-collection.php; the Newman suite skips it.",
                  "if (pm.variables.get('newman_suite')) { pm.execution.skipRequest(); }"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "X-API-Key",
                "value": "{{api_key}}",
                "description": "API Key authentication"
              },
              {
                "key": "X-Grav-Environment",
                "value": "{{grav_environment}}",
                "description": "Grav environment"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url}}{{api_prefix}}/users/:username/2fa/enable",
              "host": [
                "{{base_url}}{{api_prefix}}"
              ],
              "path": [
                "users",
                ":username",
                "2fa",
                "enable"
              ],
              "variable": [
                {
                  "key": "username",
                  "value": "{{test_username}}",
                  "description": "The username of the user account."
                }
              ]
            },
            "description": "Confirm two-factor authentication by verifying a code against the secret generated with `POST /users/{username}/2fa`, then turn 2FA on. Only the account owner can do this, since it proves possession of the secret, and it requires `api.access`. Any other caller gets 403, whether or not the username exists. Returns 500 when the Login plugin's 2FA support is not installed.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"code\": \"123456\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "id": "openapi:disableUser2FA",
          "name": "Turn off 2FA",
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "// Generated from openapi.yaml by tests/newman/sync-collection.php; the Newman suite skips it.",
                  "if (pm.variables.get('newman_suite')) { pm.execution.skipRequest(); }"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "X-API-Key",
                "value": "{{api_key}}",
                "description": "API Key authentication"
              },
              {
                "key": "X-Grav-Environment",
                "value": "{{grav_environment}}",
                "description": "Grav environment"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url}}{{api_prefix}}/users/:username/2fa/disable",
              "host": [
                "{{base_url}}{{api_prefix}}"
              ],
              "path": [
                "users",
                ":username",
                "2fa",
                "disable"
              ],
              "variable": [
                {
                  "key": "username",
                  "value": "{{test_username}}",
                  "description": "The username of the user account."
                }
              ]
            },
            "description": "Turn off two-factor authentication and clear the secret. The account owner needs `api.access` and must send a valid current `code`. A caller with `api.users.write` (or a super admin), acting through a token or key whose scope includes `api.users.write`, can turn it off for another user without a code, for lost-device recovery; only super admins can do so for a super-admin account.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"code\": \"123456\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "id": "openapi:cancelUserEmailChange",
          "name": "Cancel a waiting email change",
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "// Generated from openapi.yaml by tests/newman/sync-collection.php; the Newman suite skips it.",
                  "if (pm.variables.get('newman_suite')) { pm.execution.skipRequest(); }"
                ]
              }
            }
          ],
          "request": {
            "method": "DELETE",
            "header": [
              {
                "key": "X-API-Key",
                "value": "{{api_key}}",
                "description": "API Key authentication"
              },
              {
                "key": "X-Grav-Environment",
                "value": "{{grav_environment}}",
                "description": "Grav environment"
              }
            ],
            "url": {
              "raw": "{{base_url}}{{api_prefix}}/users/:username/email-change",
              "host": [
                "{{base_url}}{{api_prefix}}"
              ],
              "path": [
                "users",
                ":username",
                "email-change"
              ],
              "variable": [
                {
                  "key": "username",
                  "value": "{{test_username}}",
                  "description": "The username of the user account."
                }
              ]
            },
            "description": "Drop the email change that is waiting for confirmation on your own\naccount (see `PATCH /users/{username}`). The account keeps its current\naddress and the link that was mailed stops working. Needs `api.access`\nand works only on your own account (403 otherwise). Returns 404 when\nno change is waiting, or the site does not confirm email changes."
          }
        },
        {
          "id": "openapi:resendUserEmailChange",
          "name": "Mail the email change link again",
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "// Generated from openapi.yaml by tests/newman/sync-collection.php; the Newman suite skips it.",
                  "if (pm.variables.get('newman_suite')) { pm.execution.skipRequest(); }"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "X-API-Key",
                "value": "{{api_key}}",
                "description": "API Key authentication"
              },
              {
                "key": "X-Grav-Environment",
                "value": "{{grav_environment}}",
                "description": "Grav environment"
              }
            ],
            "url": {
              "raw": "{{base_url}}{{api_prefix}}/users/:username/email-change/resend",
              "host": [
                "{{base_url}}{{api_prefix}}"
              ],
              "path": [
                "users",
                ":username",
                "email-change",
                "resend"
              ],
              "variable": [
                {
                  "key": "username",
                  "value": "{{test_username}}",
                  "description": "The username of the user account."
                }
              ]
            },
            "description": "Send the confirmation link for your waiting email change again, with a\nfresh day to use it. Any earlier link stops working. Counts toward the\nLogin plugin's hourly limit of email changes (429 with `Retry-After`\nonce it is used up). Needs `api.access` and works only on your own\naccount (403 otherwise). Returns 404 when no change is waiting."
          }
        },
        {
          "id": "openapi:getCurrentUser",
          "name": "Get the current user",
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "// Generated from openapi.yaml by tests/newman/sync-collection.php; the Newman suite skips it.",
                  "if (pm.variables.get('newman_suite')) { pm.execution.skipRequest(); }"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "X-API-Key",
                "value": "{{api_key}}",
                "description": "API Key authentication"
              },
              {
                "key": "X-Grav-Environment",
                "value": "{{grav_environment}}",
                "description": "Grav environment"
              }
            ],
            "url": {
              "raw": "{{base_url}}{{api_prefix}}/me",
              "host": [
                "{{base_url}}{{api_prefix}}"
              ],
              "path": [
                "me"
              ]
            },
            "description": "Return the authenticated user's profile, resolved permission map and demo-mode state (the same `user` object the login endpoints return), plus the Grav and admin plugin versions. Requires `api.access`."
          }
        },
        {
          "id": "openapi:listInvitations",
          "name": "List pending invitations",
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "// Generated from openapi.yaml by tests/newman/sync-collection.php; the Newman suite skips it.",
                  "if (pm.variables.get('newman_suite')) { pm.execution.skipRequest(); }"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "X-API-Key",
                "value": "{{api_key}}",
                "description": "API Key authentication"
              },
              {
                "key": "X-Grav-Environment",
                "value": "{{grav_environment}}",
                "description": "Grav environment"
              }
            ],
            "url": {
              "raw": "{{base_url}}{{api_prefix}}/invitations",
              "host": [
                "{{base_url}}{{api_prefix}}"
              ],
              "path": [
                "invitations"
              ]
            },
            "description": "Returns all pending user invitations, most recent first. Expired invitations are purged from the store before listing. The pre-set `access` map is not included, but each entry carries its `token`, which is the credential the invitee uses to accept.\n\nRequires `api.users.write`, like creating, resending and revoking: the token alone accepts the invite, so read-only users cannot list them."
          }
        },
        {
          "id": "openapi:createInvitation",
          "name": "Invite a new user",
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "// Generated from openapi.yaml by tests/newman/sync-collection.php; the Newman suite skips it.",
                  "if (pm.variables.get('newman_suite')) { pm.execution.skipRequest(); }"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "X-API-Key",
                "value": "{{api_key}}",
                "description": "API Key authentication"
              },
              {
                "key": "X-Grav-Environment",
                "value": "{{grav_environment}}",
                "description": "Grav environment"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url}}{{api_prefix}}/invitations",
              "host": [
                "{{base_url}}{{api_prefix}}"
              ],
              "path": [
                "invitations"
              ]
            },
            "description": "Creates a time-limited invitation for a new account with pre-set permissions and groups, and emails the invite link when the Email plugin is configured. The recipient accepts through the public `/auth/invite/{token}` endpoints and picks their own username and password; the account gets exactly the access set here.\n\nSuper flags (`admin.super`, `api.super`, and scalar `admin`/`api` grants) are stripped from `access`, and `groups` is ignored, unless the caller is a super-admin within their API-key scope. Any earlier pending invite for the same email is replaced.\n\nIf email is not configured or sending fails, the invite is still created and the response includes `link` and a `warning` so the admin can share it manually. The link host is taken from `admin_base_url`, the Referer or the Origin header only when that origin is the site's own or listed in the API's CORS origins.\n\nRequires `api.users.write`. Demo accounts are refused. The response sets `X-Invalidates: invitations:list`.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"email\": \"jane@example.com\",\n  \"fullname\": \"Jane Doe\",\n  \"access\": {\n    \"api\": {\n      \"access\": true,\n      \"pages\": {\n        \"read\": true,\n        \"write\": true\n      }\n    }\n  },\n  \"groups\": [\n    \"editors\"\n  ],\n  \"expiration\": 604800,\n  \"message\": \"Welcome aboard!\",\n  \"admin_base_url\": \"https://example.com/admin\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "id": "openapi:deleteInvitation",
          "name": "Revoke an invitation",
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "// Generated from openapi.yaml by tests/newman/sync-collection.php; the Newman suite skips it.",
                  "if (pm.variables.get('newman_suite')) { pm.execution.skipRequest(); }"
                ]
              }
            }
          ],
          "request": {
            "method": "DELETE",
            "header": [
              {
                "key": "X-API-Key",
                "value": "{{api_key}}",
                "description": "API Key authentication"
              },
              {
                "key": "X-Grav-Environment",
                "value": "{{grav_environment}}",
                "description": "Grav environment"
              }
            ],
            "url": {
              "raw": "{{base_url}}{{api_prefix}}/invitations/:token",
              "host": [
                "{{base_url}}{{api_prefix}}"
              ],
              "path": [
                "invitations",
                ":token"
              ],
              "variable": [
                {
                  "key": "token",
                  "value": "",
                  "description": "Invitation token, as returned by `GET /invitations` or `POST /invitations`."
                }
              ]
            },
            "description": "Deletes a pending invitation so its link no longer works. Requires `api.users.write`. Demo accounts are refused. The response sets `X-Invalidates: invitations:list`."
          }
        },
        {
          "id": "openapi:resendInvitation",
          "name": "Resend an invitation email",
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "// Generated from openapi.yaml by tests/newman/sync-collection.php; the Newman suite skips it.",
                  "if (pm.variables.get('newman_suite')) { pm.execution.skipRequest(); }"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "X-API-Key",
                "value": "{{api_key}}",
                "description": "API Key authentication"
              },
              {
                "key": "X-Grav-Environment",
                "value": "{{grav_environment}}",
                "description": "Grav environment"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url}}{{api_prefix}}/invitations/:token/resend",
              "host": [
                "{{base_url}}{{api_prefix}}"
              ],
              "path": [
                "invitations",
                ":token",
                "resend"
              ],
              "variable": [
                {
                  "key": "token",
                  "value": "",
                  "description": "Invitation token, as returned by `GET /invitations` or `POST /invitations`."
                }
              ]
            },
            "description": "Sends the invitation email again for a pending, unexpired invite. The token and expiry are unchanged. Unlike `POST /invitations`, this fails with 422 when email is not configured. The link host follows the same allowlist rules as invitation creation.\n\nRequires `api.users.write`. Demo accounts are refused.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"message\": \"\",\n  \"admin_base_url\": \"\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        }
      ]
    },
    {
      "name": "System",
      "description": "System administration — info, cache, logs, and backups.",
      "item": [
        {
          "name": "Ping / Keep-Alive",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "X-API-Key",
                "value": "{{api_key}}"
              },
              {
                "key": "X-Grav-Environment",
                "value": "{{grav_environment}}"
              },
              {
                "key": "Accept",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url}}{{api_prefix}}/ping",
              "host": [
                "{{base_url}}{{api_prefix}}"
              ],
              "path": [
                "ping"
              ]
            },
            "description": "Lightweight health check endpoint. Returns {pong: true}. Useful for session keep-alive."
          }
        },
        {
          "name": "System Info",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "const res = pm.response.json();",
                  "pm.test('Status is 200', () => pm.response.to.have.status(200));",
                  "pm.test('Has grav_version', () => pm.expect(res.data).to.have.property('grav_version'));",
                  "pm.test('Has php_version', () => pm.expect(res.data).to.have.property('php_version'));",
                  "pm.test('Has plugins', () => pm.expect(res.data).to.have.property('plugins'));"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "url": {
              "raw": "{{base_url}}{{api_prefix}}/system/info",
              "host": [
                "{{base_url}}{{api_prefix}}"
              ],
              "path": [
                "system",
                "info"
              ]
            },
            "description": "Get system information: Grav version, PHP version, extensions, plugins, themes, and environment.",
            "header": [
              {
                "key": "X-API-Key",
                "value": "{{api_key}}",
                "description": "API Key authentication"
              },
              {
                "key": "X-Grav-Environment",
                "value": "{{grav_environment}}",
                "description": "Grav environment"
              }
            ]
          }
        },
        {
          "name": "Clear Cache (Standard)",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('Status is 200', () => pm.response.to.have.status(200));"
                ]
              }
            }
          ],
          "request": {
            "method": "DELETE",
            "url": {
              "raw": "{{base_url}}{{api_prefix}}/cache?scope=standard",
              "host": [
                "{{base_url}}{{api_prefix}}"
              ],
              "path": [
                "cache"
              ],
              "query": [
                {
                  "key": "scope",
                  "value": "standard",
                  "description": "Options: all, standard, images, assets, tmp"
                }
              ]
            },
            "description": "Clear the Grav cache. Scopes: `all`, `standard`, `images`, `assets`, `tmp`.",
            "header": [
              {
                "key": "X-API-Key",
                "value": "{{api_key}}",
                "description": "API Key authentication"
              },
              {
                "key": "X-Grav-Environment",
                "value": "{{grav_environment}}",
                "description": "Grav environment"
              }
            ]
          }
        },
        {
          "name": "Clear Cache (All)",
          "request": {
            "method": "DELETE",
            "url": {
              "raw": "{{base_url}}{{api_prefix}}/cache?scope=all",
              "host": [
                "{{base_url}}{{api_prefix}}"
              ],
              "path": [
                "cache"
              ],
              "query": [
                {
                  "key": "scope",
                  "value": "all"
                }
              ]
            },
            "description": "Clear ALL caches including images and assets.",
            "header": [
              {
                "key": "X-API-Key",
                "value": "{{api_key}}",
                "description": "API Key authentication"
              },
              {
                "key": "X-Grav-Environment",
                "value": "{{grav_environment}}",
                "description": "Grav environment"
              }
            ]
          }
        },
        {
          "name": "View Logs",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "const res = pm.response.json();",
                  "pm.test('Status is 200', () => pm.response.to.have.status(200));",
                  "pm.test('Has data array', () => pm.expect(res.data).to.be.an('array'));"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "url": {
              "raw": "{{base_url}}{{api_prefix}}/system/logs?page=1&per_page=50",
              "host": [
                "{{base_url}}{{api_prefix}}"
              ],
              "path": [
                "system",
                "logs"
              ],
              "query": [
                {
                  "key": "page",
                  "value": "1"
                },
                {
                  "key": "per_page",
                  "value": "50"
                },
                {
                  "key": "level",
                  "value": "error",
                  "disabled": true,
                  "description": "Filter: error, warning, info, debug"
                }
              ]
            },
            "description": "Read Grav log entries. Optional `level` filter for error/warning/info/debug.",
            "header": [
              {
                "key": "X-API-Key",
                "value": "{{api_key}}",
                "description": "API Key authentication"
              },
              {
                "key": "X-Grav-Environment",
                "value": "{{grav_environment}}",
                "description": "Grav environment"
              }
            ]
          }
        },
        {
          "name": "Clear Log",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "const res = pm.response.json();",
                  "pm.test('Status is 200', () => pm.response.to.have.status(200));",
                  "pm.test('Reports the cleared file', () => pm.expect(res.data.file).to.eql('email.log'));",
                  "pm.test('Reports how much was cleared', () => pm.expect(res.data.cleared_bytes).to.be.a('number'));"
                ]
              }
            }
          ],
          "request": {
            "method": "DELETE",
            "url": {
              "raw": "{{base_url}}{{api_prefix}}/system/logs",
              "host": [
                "{{base_url}}{{api_prefix}}"
              ],
              "path": [
                "system",
                "logs"
              ]
            },
            "description": "Empty out a log file. Super-admin only. The file is truncated and a single entry naming whoever cleared it is left behind. Targets `email.log` here so a collection run does not wipe the system log; any file listed by `GET /system/logs/files` is valid.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"file\": \"email.log\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "X-API-Key",
                "value": "{{api_key}}",
                "description": "API Key authentication"
              },
              {
                "key": "X-Grav-Environment",
                "value": "{{grav_environment}}",
                "description": "Grav environment"
              }
            ]
          }
        },
        {
          "name": "Create Backup",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('Status is 200', () => pm.response.to.have.status(200));"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "url": {
              "raw": "{{base_url}}{{api_prefix}}/system/backup",
              "host": [
                "{{base_url}}{{api_prefix}}"
              ],
              "path": [
                "system",
                "backup"
              ]
            },
            "description": "Create a new site backup.",
            "header": [
              {
                "key": "X-API-Key",
                "value": "{{api_key}}",
                "description": "API Key authentication"
              },
              {
                "key": "X-Grav-Environment",
                "value": "{{grav_environment}}",
                "description": "Grav environment"
              }
            ]
          }
        },
        {
          "name": "List Backups",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "const res = pm.response.json();",
                  "pm.test('Status is 200', () => pm.response.to.have.status(200));",
                  "pm.test('Has data array', () => pm.expect(res.data).to.be.an('array'));"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "url": {
              "raw": "{{base_url}}{{api_prefix}}/system/backups",
              "host": [
                "{{base_url}}{{api_prefix}}"
              ],
              "path": [
                "system",
                "backups"
              ]
            },
            "description": "List all available backups.",
            "header": [
              {
                "key": "X-API-Key",
                "value": "{{api_key}}",
                "description": "API Key authentication"
              },
              {
                "key": "X-Grav-Environment",
                "value": "{{grav_environment}}",
                "description": "Grav environment"
              }
            ]
          }
        },
        {
          "id": "openapi:getEnvironments",
          "name": "List available environments",
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "// Generated from openapi.yaml by tests/newman/sync-collection.php; the Newman suite skips it.",
                  "if (pm.variables.get('newman_suite')) { pm.execution.skipRequest(); }"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "X-API-Key",
                "value": "{{api_key}}",
                "description": "API Key authentication"
              },
              {
                "key": "X-Grav-Environment",
                "value": "{{grav_environment}}",
                "description": "Grav environment"
              }
            ],
            "url": {
              "raw": "{{base_url}}{{api_prefix}}/system/environments",
              "host": [
                "{{base_url}}{{api_prefix}}"
              ],
              "path": [
                "system",
                "environments"
              ]
            },
            "description": "Returns the environment Grav detected for this request and the\nenvironments that can be selected as configuration write targets.\nRequires `api.system.read`.\n\nThe first entry is always the base configuration (`user/config`),\nwith `name: \"\"` and `label: \"Default\"`. Every other entry is an\nexisting `user/env/<name>/` folder (legacy `user/<host>/config/`\nlayouts are included too). The `hasOverrides` flag is always `false`\non the base row."
          }
        },
        {
          "id": "openapi:createEnvironment",
          "name": "Create an environment",
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "// Generated from openapi.yaml by tests/newman/sync-collection.php; the Newman suite skips it.",
                  "if (pm.variables.get('newman_suite')) { pm.execution.skipRequest(); }"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "X-API-Key",
                "value": "{{api_key}}",
                "description": "API Key authentication"
              },
              {
                "key": "X-Grav-Environment",
                "value": "{{grav_environment}}",
                "description": "Grav environment"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url}}{{api_prefix}}/system/environments",
              "host": [
                "{{base_url}}{{api_prefix}}"
              ],
              "path": [
                "system",
                "environments"
              ]
            },
            "description": "Creates an empty environment folder at `user/env/<name>/config`. Requires `api.config.write`. The name must start with a letter or digit and contain only letters, digits, `.`, `_` and `-`. The names `default` and `base` are reserved, and an existing environment cannot be created again (both return 422).",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"name\": \"staging.example.com\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "id": "openapi:deleteEnvironment",
          "name": "Delete an environment",
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "// Generated from openapi.yaml by tests/newman/sync-collection.php; the Newman suite skips it.",
                  "if (pm.variables.get('newman_suite')) { pm.execution.skipRequest(); }"
                ]
              }
            }
          ],
          "request": {
            "method": "DELETE",
            "header": [
              {
                "key": "X-API-Key",
                "value": "{{api_key}}",
                "description": "API Key authentication"
              },
              {
                "key": "X-Grav-Environment",
                "value": "{{grav_environment}}",
                "description": "Grav environment"
              }
            ],
            "url": {
              "raw": "{{base_url}}{{api_prefix}}/system/environments/:name",
              "host": [
                "{{base_url}}{{api_prefix}}"
              ],
              "path": [
                "system",
                "environments",
                ":name"
              ],
              "variable": [
                {
                  "key": "name",
                  "value": "",
                  "description": "Environment name."
                }
              ]
            },
            "description": "Recursively deletes the environment folder `user/env/<name>/` and everything in it. Requires API super authority (not just `api.config.write`), because the folder can hold `system` and `security` overrides that only a super user may write.\nReturns 404 when no `user/env/<name>/` folder exists. Refused with 422 when the name is invalid, when it is the environment Grav resolved for the current request, or when it only exists in the legacy `user/<name>/` layout, which must be removed by hand. A folder that is a symlink pointing outside `user/env/` is also refused."
          }
        },
        {
          "id": "openapi:listLogFiles",
          "name": "List readable log files",
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "// Generated from openapi.yaml by tests/newman/sync-collection.php; the Newman suite skips it.",
                  "if (pm.variables.get('newman_suite')) { pm.execution.skipRequest(); }"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "X-API-Key",
                "value": "{{api_key}}",
                "description": "API Key authentication"
              },
              {
                "key": "X-Grav-Environment",
                "value": "{{grav_environment}}",
                "description": "Grav environment"
              }
            ],
            "url": {
              "raw": "{{base_url}}{{api_prefix}}/system/logs/files",
              "host": [
                "{{base_url}}{{api_prefix}}"
              ],
              "path": [
                "system",
                "logs",
                "files"
              ]
            },
            "description": "Returns the log files the admin log viewer can open. Requires `api.system.read`, and is refused with 403 for demo accounts. The core logs (`grav.log`, `security.log`, `email.log`, `scheduler.log`) are always listed first, even before they exist; any other `*.log` in the `log://` folder follows, then files plugins add through the `onApiLogFiles` event. Entries are de-duplicated by file name."
          }
        },
        {
          "id": "openapi:deleteBackup",
          "name": "Delete a backup",
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "// Generated from openapi.yaml by tests/newman/sync-collection.php; the Newman suite skips it.",
                  "if (pm.variables.get('newman_suite')) { pm.execution.skipRequest(); }"
                ]
              }
            }
          ],
          "request": {
            "method": "DELETE",
            "header": [
              {
                "key": "X-API-Key",
                "value": "{{api_key}}",
                "description": "API Key authentication"
              },
              {
                "key": "X-Grav-Environment",
                "value": "{{grav_environment}}",
                "description": "Grav environment"
              }
            ],
            "url": {
              "raw": "{{base_url}}{{api_prefix}}/system/backups/:filename",
              "host": [
                "{{base_url}}{{api_prefix}}"
              ],
              "path": [
                "system",
                "backups",
                ":filename"
              ],
              "variable": [
                {
                  "key": "filename",
                  "value": "",
                  "description": "Backup archive file name (a plain `.zip` name, no path)."
                }
              ]
            },
            "description": "Deletes a backup archive from the `backup://` folder. Requires `api.system.backup`, and is refused with 403 for demo accounts. The filename must be a plain `.zip` name with no path components."
          }
        },
        {
          "id": "openapi:downloadBackup",
          "name": "Download a backup",
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "// Generated from openapi.yaml by tests/newman/sync-collection.php; the Newman suite skips it.",
                  "if (pm.variables.get('newman_suite')) { pm.execution.skipRequest(); }"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "X-API-Key",
                "value": "{{api_key}}",
                "description": "API Key authentication"
              },
              {
                "key": "X-Grav-Environment",
                "value": "{{grav_environment}}",
                "description": "Grav environment"
              }
            ],
            "url": {
              "raw": "{{base_url}}{{api_prefix}}/system/backups/:filename/download",
              "host": [
                "{{base_url}}{{api_prefix}}"
              ],
              "path": [
                "system",
                "backups",
                ":filename",
                "download"
              ],
              "variable": [
                {
                  "key": "filename",
                  "value": "",
                  "description": "Backup archive file name (a plain `.zip` name, no path)."
                }
              ]
            },
            "description": "Streams a backup archive as a file download. Requires `api.system.backup`, and is refused with 403 for demo accounts. The filename must be a plain `.zip` name with no path components. Errors are returned as `application/problem+json`."
          }
        }
      ]
    },
    {
      "name": "Languages",
      "description": "Site language configuration and multi-language support.",
      "item": [
        {
          "name": "List Site Languages",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "const res = pm.response.json();",
                  "pm.test('Status is 200', () => pm.response.to.have.status(200));",
                  "pm.test('Has data', () => pm.expect(res).to.have.property('data'));"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "url": {
              "raw": "{{base_url}}{{api_prefix}}/languages",
              "host": [
                "{{base_url}}{{api_prefix}}"
              ],
              "path": [
                "languages"
              ]
            },
            "description": "List all configured site languages and their status.",
            "header": [
              {
                "key": "X-API-Key",
                "value": "{{api_key}}",
                "description": "API Key authentication"
              },
              {
                "key": "X-Grav-Environment",
                "value": "{{grav_environment}}",
                "description": "Grav environment"
              }
            ]
          }
        },
        {
          "id": "openapi:listAdminLanguages",
          "name": "List admin interface languages",
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "// Generated from openapi.yaml by tests/newman/sync-collection.php; the Newman suite skips it.",
                  "if (pm.variables.get('newman_suite')) { pm.execution.skipRequest(); }"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "X-API-Key",
                "value": "{{api_key}}",
                "description": "API Key authentication"
              },
              {
                "key": "X-Grav-Environment",
                "value": "{{grav_environment}}",
                "description": "Grav environment"
              }
            ],
            "url": {
              "raw": "{{base_url}}{{api_prefix}}/admin/languages",
              "host": [
                "{{base_url}}{{api_prefix}}"
              ],
              "path": [
                "admin",
                "languages"
              ]
            },
            "description": "Returns the locales the admin interface itself can be shown in, one per `languages/*.yaml` file shipped by the admin2 plugin (as opposed to `GET /languages`, which lists the site's content languages). Requires `api.system.read`. Sorted by native name; an empty list when admin2 is not installed."
          }
        }
      ]
    },
    {
      "name": "Pages / Multi-Language",
      "description": "Multi-language page operations — translations, language-specific content.",
      "item": [
        {
          "name": "Get Page in Language",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "const res = pm.response.json();",
                  "pm.test('Status is 200', () => pm.response.to.have.status(200));",
                  "pm.test('Has data', () => pm.expect(res).to.have.property('data'));"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "url": {
              "raw": "{{base_url}}{{api_prefix}}/pages/{{page_route}}?lang={{lang}}",
              "host": [
                "{{base_url}}{{api_prefix}}"
              ],
              "path": [
                "pages",
                "{{page_route}}"
              ],
              "query": [
                {
                  "key": "lang",
                  "value": "{{lang}}"
                }
              ]
            },
            "description": "Get a page in a specific language.",
            "header": [
              {
                "key": "X-API-Key",
                "value": "{{api_key}}",
                "description": "API Key authentication"
              },
              {
                "key": "X-Grav-Environment",
                "value": "{{grav_environment}}",
                "description": "Grav environment"
              }
            ]
          }
        },
        {
          "name": "Get Page Translations",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "const res = pm.response.json();",
                  "pm.test('Status is 200', () => pm.response.to.have.status(200));",
                  "pm.test('Has data', () => pm.expect(res).to.have.property('data'));"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "url": {
              "raw": "{{base_url}}{{api_prefix}}/pages/{{page_route}}/languages",
              "host": [
                "{{base_url}}{{api_prefix}}"
              ],
              "path": [
                "pages",
                "{{page_route}}",
                "languages"
              ]
            },
            "description": "List all available translations for a page.",
            "header": [
              {
                "key": "X-API-Key",
                "value": "{{api_key}}",
                "description": "API Key authentication"
              },
              {
                "key": "X-Grav-Environment",
                "value": "{{grav_environment}}",
                "description": "Grav environment"
              }
            ]
          }
        },
        {
          "name": "Create Translation",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('Status is 201', () => pm.response.to.have.status(201));"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "X-API-Key",
                "value": "{{api_key}}",
                "description": "API Key authentication"
              },
              {
                "key": "X-Grav-Environment",
                "value": "{{grav_environment}}",
                "description": "Grav environment"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n    \"lang\": \"fr\",\n    \"title\": \"Mon titre\",\n    \"content\": \"# Contenu\"\n}"
            },
            "url": {
              "raw": "{{base_url}}{{api_prefix}}/pages/{{page_route}}/translate",
              "host": [
                "{{base_url}}{{api_prefix}}"
              ],
              "path": [
                "pages",
                "{{page_route}}",
                "translate"
              ]
            },
            "description": "Create a new translation for a page in a specific language."
          }
        },
        {
          "name": "Update Page in Language",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('Status is 200', () => pm.response.to.have.status(200));"
                ]
              }
            }
          ],
          "request": {
            "method": "PATCH",
            "header": [
              {
                "key": "X-API-Key",
                "value": "{{api_key}}",
                "description": "API Key authentication"
              },
              {
                "key": "X-Grav-Environment",
                "value": "{{grav_environment}}",
                "description": "Grav environment"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n    \"title\": \"Updated\"\n}"
            },
            "url": {
              "raw": "{{base_url}}{{api_prefix}}/pages/{{page_route}}?lang={{lang}}",
              "host": [
                "{{base_url}}{{api_prefix}}"
              ],
              "path": [
                "pages",
                "{{page_route}}"
              ],
              "query": [
                {
                  "key": "lang",
                  "value": "{{lang}}"
                }
              ]
            },
            "description": "Update a page in a specific language."
          }
        },
        {
          "name": "Delete Language File",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('Status is 204', () => pm.response.to.have.status(204));"
                ]
              }
            }
          ],
          "request": {
            "method": "DELETE",
            "url": {
              "raw": "{{base_url}}{{api_prefix}}/pages/{{page_route}}?lang=fr",
              "host": [
                "{{base_url}}{{api_prefix}}"
              ],
              "path": [
                "pages",
                "{{page_route}}"
              ],
              "query": [
                {
                  "key": "lang",
                  "value": "fr"
                }
              ]
            },
            "description": "Delete a specific language file for a page.",
            "header": [
              {
                "key": "X-API-Key",
                "value": "{{api_key}}",
                "description": "API Key authentication"
              },
              {
                "key": "X-Grav-Environment",
                "value": "{{grav_environment}}",
                "description": "Grav environment"
              }
            ]
          }
        }
      ]
    },
    {
      "name": "Pages / Batch Operations",
      "description": "Batch page operations — reorder children, bulk publish, bulk delete.",
      "item": [
        {
          "name": "Reorder Children",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('Status is 200', () => pm.response.to.have.status(200));"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "X-API-Key",
                "value": "{{api_key}}",
                "description": "API Key authentication"
              },
              {
                "key": "X-Grav-Environment",
                "value": "{{grav_environment}}",
                "description": "Grav environment"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n    \"order\": [\"sunshine-in-the-hills\", \"london-at-night\", \"forest-in-the-fog\"]\n}"
            },
            "url": {
              "raw": "{{base_url}}{{api_prefix}}/pages/blog/reorder",
              "host": [
                "{{base_url}}{{api_prefix}}"
              ],
              "path": [
                "pages",
                "blog",
                "reorder"
              ]
            },
            "description": "Reorder child pages under a parent page."
          }
        },
        {
          "name": "Batch Publish",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('Status is 200', () => pm.response.to.have.status(200));"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "X-API-Key",
                "value": "{{api_key}}",
                "description": "API Key authentication"
              },
              {
                "key": "X-Grav-Environment",
                "value": "{{grav_environment}}",
                "description": "Grav environment"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n    \"operation\": \"publish\",\n    \"routes\": [\"/newman-test-page\"]\n}"
            },
            "url": {
              "raw": "{{base_url}}{{api_prefix}}/pages/batch",
              "host": [
                "{{base_url}}{{api_prefix}}"
              ],
              "path": [
                "pages",
                "batch"
              ]
            },
            "description": "Batch publish multiple pages at once."
          }
        },
        {
          "name": "Batch Delete",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('Status is 200', () => pm.response.to.have.status(200));"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "X-API-Key",
                "value": "{{api_key}}",
                "description": "API Key authentication"
              },
              {
                "key": "X-Grav-Environment",
                "value": "{{grav_environment}}",
                "description": "Grav environment"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"operation\": \"delete\",\n  \"routes\": [\n    \"/newman-test-copy\"\n  ]\n}"
            },
            "url": {
              "raw": "{{base_url}}{{api_prefix}}/pages/batch",
              "host": [
                "{{base_url}}{{api_prefix}}"
              ],
              "path": [
                "pages",
                "batch"
              ]
            },
            "description": "Batch delete multiple pages at once."
          }
        },
        {
          "name": "Reorganize Pages",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "const res = pm.response.json();",
                  "pm.test('Status is 200', () => pm.response.to.have.status(200));",
                  "pm.test('Has data array', () => pm.expect(res.data).to.be.an('array'));",
                  "pm.test('Each result has route and slug', () => {",
                  "    res.data.forEach(item => {",
                  "        pm.expect(item).to.have.property('route');",
                  "        pm.expect(item).to.have.property('slug');",
                  "        pm.expect(item).to.have.property('parent');",
                  "    });",
                  "});"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "X-API-Key",
                "value": "{{api_key}}",
                "description": "API Key authentication"
              },
              {
                "key": "X-Grav-Environment",
                "value": "{{grav_environment}}",
                "description": "Grav environment"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n    \"operations\": [\n        { \"route\": \"/blog/sunshine-in-the-hills\", \"position\": 2 },\n        { \"route\": \"/blog/london-at-night\", \"position\": 1 }\n    ]\n}"
            },
            "url": {
              "raw": "{{base_url}}{{api_prefix}}/pages/reorganize",
              "host": [
                "{{base_url}}{{api_prefix}}"
              ],
              "path": [
                "pages",
                "reorganize"
              ]
            },
            "description": "Reorganize multiple pages atomically — move across parents and/or reorder within the same request. Each operation specifies a page route and optionally a new parent and/or position. All operations are validated before any changes are applied."
          }
        }
      ]
    },
    {
      "name": "GPM",
      "description": "Grav Package Manager — manage plugins, themes, updates, and the repository.",
      "item": [
        {
          "name": "GPM / Installed",
          "description": "List and inspect installed plugins and themes.",
          "item": [
            {
              "name": "List Installed Plugins",
              "event": [
                {
                  "listen": "test",
                  "script": {
                    "type": "text/javascript",
                    "exec": [
                      "const res = pm.response.json();",
                      "pm.test('Status is 200', () => pm.response.to.have.status(200));",
                      "pm.test('Has data', () => pm.expect(res).to.have.property('data'));"
                    ]
                  }
                }
              ],
              "request": {
                "method": "GET",
                "url": {
                  "raw": "{{base_url}}{{api_prefix}}/gpm/plugins",
                  "host": [
                    "{{base_url}}{{api_prefix}}"
                  ],
                  "path": [
                    "gpm",
                    "plugins"
                  ]
                },
                "description": "List all installed plugins with their versions and status.",
                "header": [
                  {
                    "key": "X-API-Key",
                    "value": "{{api_key}}",
                    "description": "API Key authentication"
                  },
                  {
                    "key": "X-Grav-Environment",
                    "value": "{{grav_environment}}",
                    "description": "Grav environment"
                  }
                ]
              }
            },
            {
              "name": "Plugin Details",
              "event": [
                {
                  "listen": "test",
                  "script": {
                    "type": "text/javascript",
                    "exec": [
                      "const res = pm.response.json();",
                      "pm.test('Status is 200', () => pm.response.to.have.status(200));",
                      "pm.test('Has data', () => pm.expect(res).to.have.property('data'));"
                    ]
                  }
                }
              ],
              "request": {
                "method": "GET",
                "url": {
                  "raw": "{{base_url}}{{api_prefix}}/gpm/plugins/{{package_slug}}",
                  "host": [
                    "{{base_url}}{{api_prefix}}"
                  ],
                  "path": [
                    "gpm",
                    "plugins",
                    "{{package_slug}}"
                  ]
                },
                "description": "Get details for a specific installed plugin.",
                "header": [
                  {
                    "key": "X-API-Key",
                    "value": "{{api_key}}",
                    "description": "API Key authentication"
                  },
                  {
                    "key": "X-Grav-Environment",
                    "value": "{{grav_environment}}",
                    "description": "Grav environment"
                  }
                ]
              }
            },
            {
              "name": "List Installed Themes",
              "event": [
                {
                  "listen": "test",
                  "script": {
                    "type": "text/javascript",
                    "exec": [
                      "const res = pm.response.json();",
                      "pm.test('Status is 200', () => pm.response.to.have.status(200));",
                      "pm.test('Has data', () => pm.expect(res).to.have.property('data'));"
                    ]
                  }
                }
              ],
              "request": {
                "method": "GET",
                "url": {
                  "raw": "{{base_url}}{{api_prefix}}/gpm/themes",
                  "host": [
                    "{{base_url}}{{api_prefix}}"
                  ],
                  "path": [
                    "gpm",
                    "themes"
                  ]
                },
                "description": "List all installed themes with their versions and status.",
                "header": [
                  {
                    "key": "X-API-Key",
                    "value": "{{api_key}}",
                    "description": "API Key authentication"
                  },
                  {
                    "key": "X-Grav-Environment",
                    "value": "{{grav_environment}}",
                    "description": "Grav environment"
                  }
                ]
              }
            },
            {
              "name": "Theme Details",
              "event": [
                {
                  "listen": "test",
                  "script": {
                    "type": "text/javascript",
                    "exec": [
                      "const res = pm.response.json();",
                      "pm.test('Status is 200', () => pm.response.to.have.status(200));",
                      "pm.test('Has data', () => pm.expect(res).to.have.property('data'));"
                    ]
                  }
                }
              ],
              "request": {
                "method": "GET",
                "url": {
                  "raw": "{{base_url}}{{api_prefix}}/gpm/themes/{{package_slug}}",
                  "host": [
                    "{{base_url}}{{api_prefix}}"
                  ],
                  "path": [
                    "gpm",
                    "themes",
                    "{{package_slug}}"
                  ]
                },
                "description": "Get details for a specific installed theme.",
                "header": [
                  {
                    "key": "X-API-Key",
                    "value": "{{api_key}}",
                    "description": "API Key authentication"
                  },
                  {
                    "key": "X-Grav-Environment",
                    "value": "{{grav_environment}}",
                    "description": "Grav environment"
                  }
                ]
              }
            },
            {
              "name": "Plugin README",
              "request": {
                "method": "GET",
                "header": [
                  {
                    "key": "X-API-Key",
                    "value": "{{api_key}}"
                  },
                  {
                    "key": "X-Grav-Environment",
                    "value": "{{grav_environment}}"
                  },
                  {
                    "key": "Accept",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{base_url}}{{api_prefix}}/gpm/plugins/{{package_slug}}/readme",
                  "host": [
                    "{{base_url}}{{api_prefix}}"
                  ],
                  "path": [
                    "gpm",
                    "plugins",
                    "{{package_slug}}",
                    "readme"
                  ]
                },
                "description": "Get the README.md content for an installed plugin."
              }
            },
            {
              "name": "Plugin Changelog",
              "request": {
                "method": "GET",
                "header": [
                  {
                    "key": "X-API-Key",
                    "value": "{{api_key}}"
                  },
                  {
                    "key": "X-Grav-Environment",
                    "value": "{{grav_environment}}"
                  },
                  {
                    "key": "Accept",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{base_url}}{{api_prefix}}/gpm/plugins/{{package_slug}}/changelog",
                  "host": [
                    "{{base_url}}{{api_prefix}}"
                  ],
                  "path": [
                    "gpm",
                    "plugins",
                    "{{package_slug}}",
                    "changelog"
                  ]
                },
                "description": "Get the CHANGELOG.md content for an installed plugin."
              }
            },
            {
              "name": "Theme README",
              "request": {
                "method": "GET",
                "header": [
                  {
                    "key": "X-API-Key",
                    "value": "{{api_key}}"
                  },
                  {
                    "key": "X-Grav-Environment",
                    "value": "{{grav_environment}}"
                  },
                  {
                    "key": "Accept",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{base_url}}{{api_prefix}}/gpm/themes/{{package_slug}}/readme",
                  "host": [
                    "{{base_url}}{{api_prefix}}"
                  ],
                  "path": [
                    "gpm",
                    "themes",
                    "{{package_slug}}",
                    "readme"
                  ]
                },
                "description": "Get the README.md content for an installed theme."
              }
            },
            {
              "name": "Theme Changelog",
              "request": {
                "method": "GET",
                "header": [
                  {
                    "key": "X-API-Key",
                    "value": "{{api_key}}"
                  },
                  {
                    "key": "X-Grav-Environment",
                    "value": "{{grav_environment}}"
                  },
                  {
                    "key": "Accept",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{base_url}}{{api_prefix}}/gpm/themes/{{package_slug}}/changelog",
                  "host": [
                    "{{base_url}}{{api_prefix}}"
                  ],
                  "path": [
                    "gpm",
                    "themes",
                    "{{package_slug}}",
                    "changelog"
                  ]
                },
                "description": "Get the CHANGELOG.md content for an installed theme."
              }
            }
          ]
        },
        {
          "name": "GPM / Updates",
          "description": "Check for available updates.",
          "item": [
            {
              "name": "Check Updates",
              "event": [
                {
                  "listen": "test",
                  "script": {
                    "type": "text/javascript",
                    "exec": [
                      "const res = pm.response.json();",
                      "pm.test('Status is 200', () => pm.response.to.have.status(200));",
                      "pm.test('Has data', () => pm.expect(res).to.have.property('data'));"
                    ]
                  }
                }
              ],
              "request": {
                "method": "GET",
                "url": {
                  "raw": "{{base_url}}{{api_prefix}}/gpm/updates",
                  "host": [
                    "{{base_url}}{{api_prefix}}"
                  ],
                  "path": [
                    "gpm",
                    "updates"
                  ]
                },
                "description": "Check for available updates to plugins, themes, and Grav core.",
                "header": [
                  {
                    "key": "X-API-Key",
                    "value": "{{api_key}}",
                    "description": "API Key authentication"
                  },
                  {
                    "key": "X-Grav-Environment",
                    "value": "{{grav_environment}}",
                    "description": "Grav environment"
                  }
                ]
              }
            },
            {
              "name": "Check Updates (flush cache)",
              "event": [
                {
                  "listen": "test",
                  "script": {
                    "type": "text/javascript",
                    "exec": [
                      "const res = pm.response.json();",
                      "pm.test('Status is 200', () => pm.response.to.have.status(200));",
                      "pm.test('Has data', () => pm.expect(res).to.have.property('data'));"
                    ]
                  }
                }
              ],
              "request": {
                "method": "GET",
                "url": {
                  "raw": "{{base_url}}{{api_prefix}}/gpm/updates?flush=true",
                  "host": [
                    "{{base_url}}{{api_prefix}}"
                  ],
                  "path": [
                    "gpm",
                    "updates"
                  ],
                  "query": [
                    {
                      "key": "flush",
                      "value": "true"
                    }
                  ]
                },
                "description": "Check for updates with cache flushed to get the latest information.",
                "header": [
                  {
                    "key": "X-API-Key",
                    "value": "{{api_key}}",
                    "description": "API Key authentication"
                  },
                  {
                    "key": "X-Grav-Environment",
                    "value": "{{grav_environment}}",
                    "description": "Grav environment"
                  }
                ]
              }
            }
          ]
        },
        {
          "name": "GPM / Install & Remove",
          "description": "Install and remove packages.",
          "item": [
            {
              "name": "Install Plugin",
              "event": [
                {
                  "listen": "test",
                  "script": {
                    "type": "text/javascript",
                    "exec": [
                      "pm.test('Status is 200', () => pm.response.to.have.status(200));"
                    ]
                  }
                }
              ],
              "request": {
                "method": "POST",
                "header": [
                  {
                    "key": "X-API-Key",
                    "value": "{{api_key}}",
                    "description": "API Key authentication"
                  },
                  {
                    "key": "X-Grav-Environment",
                    "value": "{{grav_environment}}",
                    "description": "Grav environment"
                  },
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "body": {
                  "mode": "raw",
                  "raw": "{\n    \"package\": \"admin\",\n    \"type\": \"plugin\"\n}"
                },
                "url": {
                  "raw": "{{base_url}}{{api_prefix}}/gpm/install",
                  "host": [
                    "{{base_url}}{{api_prefix}}"
                  ],
                  "path": [
                    "gpm",
                    "install"
                  ]
                },
                "description": "Install a plugin from the GPM repository. For premium packages, add a `license` field: `\"license\": \"XXXXXXXX-XXXXXXXX-XXXXXXXX-XXXXXXXX\"`."
              }
            },
            {
              "name": "Install Theme",
              "event": [
                {
                  "listen": "test",
                  "script": {
                    "type": "text/javascript",
                    "exec": [
                      "pm.test('Status is 200', () => pm.response.to.have.status(200));"
                    ]
                  }
                }
              ],
              "request": {
                "method": "POST",
                "header": [
                  {
                    "key": "X-API-Key",
                    "value": "{{api_key}}",
                    "description": "API Key authentication"
                  },
                  {
                    "key": "X-Grav-Environment",
                    "value": "{{grav_environment}}",
                    "description": "Grav environment"
                  },
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "body": {
                  "mode": "raw",
                  "raw": "{\n    \"package\": \"quark\",\n    \"type\": \"theme\"\n}"
                },
                "url": {
                  "raw": "{{base_url}}{{api_prefix}}/gpm/install",
                  "host": [
                    "{{base_url}}{{api_prefix}}"
                  ],
                  "path": [
                    "gpm",
                    "install"
                  ]
                },
                "description": "Install a theme from the GPM repository. For premium themes, add a `license` field: `\"license\": \"XXXXXXXX-XXXXXXXX-XXXXXXXX-XXXXXXXX\"`."
              }
            },
            {
              "name": "Remove Package",
              "event": [
                {
                  "listen": "test",
                  "script": {
                    "type": "text/javascript",
                    "exec": [
                      "pm.test('Status is 200', () => pm.response.to.have.status(200));"
                    ]
                  }
                }
              ],
              "request": {
                "method": "POST",
                "header": [
                  {
                    "key": "X-API-Key",
                    "value": "{{api_key}}",
                    "description": "API Key authentication"
                  },
                  {
                    "key": "X-Grav-Environment",
                    "value": "{{grav_environment}}",
                    "description": "Grav environment"
                  },
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "body": {
                  "mode": "raw",
                  "raw": "{\n    \"package\": \"some-plugin\"\n}"
                },
                "url": {
                  "raw": "{{base_url}}{{api_prefix}}/gpm/remove",
                  "host": [
                    "{{base_url}}{{api_prefix}}"
                  ],
                  "path": [
                    "gpm",
                    "remove"
                  ]
                },
                "description": "Remove an installed plugin or theme."
              }
            },
            {
              "name": "Direct Install from URL",
              "event": [
                {
                  "listen": "test",
                  "script": {
                    "type": "text/javascript",
                    "exec": [
                      "pm.test('Status is 200', () => pm.response.to.have.status(200));"
                    ]
                  }
                }
              ],
              "request": {
                "method": "POST",
                "header": [
                  {
                    "key": "X-API-Key",
                    "value": "{{api_key}}",
                    "description": "API Key authentication"
                  },
                  {
                    "key": "X-Grav-Environment",
                    "value": "{{grav_environment}}",
                    "description": "Grav environment"
                  },
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "body": {
                  "mode": "raw",
                  "raw": "{\n    \"url\": \"https://example.com/package.zip\"\n}"
                },
                "url": {
                  "raw": "{{base_url}}{{api_prefix}}/gpm/direct-install",
                  "host": [
                    "{{base_url}}{{api_prefix}}"
                  ],
                  "path": [
                    "gpm",
                    "direct-install"
                  ]
                },
                "description": "Install a package directly from a URL (zip file)."
              }
            }
          ]
        },
        {
          "name": "GPM / Update",
          "description": "Update packages and Grav core.",
          "item": [
            {
              "name": "Update Package",
              "event": [
                {
                  "listen": "test",
                  "script": {
                    "type": "text/javascript",
                    "exec": [
                      "pm.test('Status is 200', () => pm.response.to.have.status(200));"
                    ]
                  }
                }
              ],
              "request": {
                "method": "POST",
                "header": [
                  {
                    "key": "X-API-Key",
                    "value": "{{api_key}}",
                    "description": "API Key authentication"
                  },
                  {
                    "key": "X-Grav-Environment",
                    "value": "{{grav_environment}}",
                    "description": "Grav environment"
                  },
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "body": {
                  "mode": "raw",
                  "raw": "{\n    \"package\": \"admin\"\n}"
                },
                "url": {
                  "raw": "{{base_url}}{{api_prefix}}/gpm/update",
                  "host": [
                    "{{base_url}}{{api_prefix}}"
                  ],
                  "path": [
                    "gpm",
                    "update"
                  ]
                },
                "description": "Update a specific package to the latest version."
              }
            },
            {
              "name": "Update All",
              "event": [
                {
                  "listen": "test",
                  "script": {
                    "type": "text/javascript",
                    "exec": [
                      "pm.test('Status is 200', () => pm.response.to.have.status(200));"
                    ]
                  }
                }
              ],
              "request": {
                "method": "POST",
                "header": [
                  {
                    "key": "X-API-Key",
                    "value": "{{api_key}}",
                    "description": "API Key authentication"
                  },
                  {
                    "key": "X-Grav-Environment",
                    "value": "{{grav_environment}}",
                    "description": "Grav environment"
                  }
                ],
                "url": {
                  "raw": "{{base_url}}{{api_prefix}}/gpm/update-all",
                  "host": [
                    "{{base_url}}{{api_prefix}}"
                  ],
                  "path": [
                    "gpm",
                    "update-all"
                  ]
                },
                "description": "Update all installed packages to their latest versions."
              }
            },
            {
              "name": "Upgrade Grav",
              "event": [
                {
                  "listen": "test",
                  "script": {
                    "type": "text/javascript",
                    "exec": [
                      "pm.test('Status is 200', () => pm.response.to.have.status(200));"
                    ]
                  }
                }
              ],
              "request": {
                "method": "POST",
                "header": [
                  {
                    "key": "X-API-Key",
                    "value": "{{api_key}}",
                    "description": "API Key authentication"
                  },
                  {
                    "key": "X-Grav-Environment",
                    "value": "{{grav_environment}}",
                    "description": "Grav environment"
                  }
                ],
                "url": {
                  "raw": "{{base_url}}{{api_prefix}}/gpm/upgrade",
                  "host": [
                    "{{base_url}}{{api_prefix}}"
                  ],
                  "path": [
                    "gpm",
                    "upgrade"
                  ]
                },
                "description": "Upgrade Grav core to the latest version."
              }
            }
          ]
        },
        {
          "name": "GPM / Repository",
          "description": "Browse and search the GPM repository for available packages.",
          "item": [
            {
              "name": "Search Packages",
              "event": [
                {
                  "listen": "test",
                  "script": {
                    "type": "text/javascript",
                    "exec": [
                      "const res = pm.response.json();",
                      "pm.test('Status is 200', () => pm.response.to.have.status(200));",
                      "pm.test('Has data', () => pm.expect(res).to.have.property('data'));"
                    ]
                  }
                }
              ],
              "request": {
                "method": "GET",
                "url": {
                  "raw": "{{base_url}}{{api_prefix}}/gpm/search?q=email",
                  "host": [
                    "{{base_url}}{{api_prefix}}"
                  ],
                  "path": [
                    "gpm",
                    "search"
                  ],
                  "query": [
                    {
                      "key": "q",
                      "value": "email",
                      "description": "Search query (matches slug, name, description, author, keywords)"
                    }
                  ]
                },
                "description": "Search across all plugins and themes in the GPM repository by keyword.",
                "header": [
                  {
                    "key": "X-API-Key",
                    "value": "{{api_key}}",
                    "description": "API Key authentication"
                  },
                  {
                    "key": "X-Grav-Environment",
                    "value": "{{grav_environment}}",
                    "description": "Grav environment"
                  }
                ]
              }
            },
            {
              "name": "Browse Repository Plugins",
              "event": [
                {
                  "listen": "test",
                  "script": {
                    "type": "text/javascript",
                    "exec": [
                      "const res = pm.response.json();",
                      "pm.test('Status is 200', () => pm.response.to.have.status(200));",
                      "pm.test('Has data', () => pm.expect(res).to.have.property('data'));"
                    ]
                  }
                }
              ],
              "request": {
                "method": "GET",
                "url": {
                  "raw": "{{base_url}}{{api_prefix}}/gpm/repository/plugins",
                  "host": [
                    "{{base_url}}{{api_prefix}}"
                  ],
                  "path": [
                    "gpm",
                    "repository",
                    "plugins"
                  ],
                  "query": [
                    {
                      "key": "q",
                      "value": "",
                      "description": "Optional keyword filter (matches slug, name, description, author, keywords)",
                      "disabled": true
                    }
                  ]
                },
                "description": "Browse available plugins in the GPM repository. Use ?q=keyword to filter.",
                "header": [
                  {
                    "key": "X-API-Key",
                    "value": "{{api_key}}",
                    "description": "API Key authentication"
                  },
                  {
                    "key": "X-Grav-Environment",
                    "value": "{{grav_environment}}",
                    "description": "Grav environment"
                  }
                ]
              }
            },
            {
              "name": "Browse Repository Themes",
              "event": [
                {
                  "listen": "test",
                  "script": {
                    "type": "text/javascript",
                    "exec": [
                      "const res = pm.response.json();",
                      "pm.test('Status is 200', () => pm.response.to.have.status(200));",
                      "pm.test('Has data', () => pm.expect(res).to.have.property('data'));"
                    ]
                  }
                }
              ],
              "request": {
                "method": "GET",
                "url": {
                  "raw": "{{base_url}}{{api_prefix}}/gpm/repository/themes",
                  "host": [
                    "{{base_url}}{{api_prefix}}"
                  ],
                  "path": [
                    "gpm",
                    "repository",
                    "themes"
                  ],
                  "query": [
                    {
                      "key": "q",
                      "value": "",
                      "description": "Optional keyword filter (matches slug, name, description, author, keywords)",
                      "disabled": true
                    }
                  ]
                },
                "description": "Browse available themes in the GPM repository. Use ?q=keyword to filter.",
                "header": [
                  {
                    "key": "X-API-Key",
                    "value": "{{api_key}}",
                    "description": "API Key authentication"
                  },
                  {
                    "key": "X-Grav-Environment",
                    "value": "{{grav_environment}}",
                    "description": "Grav environment"
                  }
                ]
              }
            },
            {
              "name": "Repository Package Details",
              "event": [
                {
                  "listen": "test",
                  "script": {
                    "type": "text/javascript",
                    "exec": [
                      "const res = pm.response.json();",
                      "pm.test('Status is 200', () => pm.response.to.have.status(200));",
                      "pm.test('Has data', () => pm.expect(res).to.have.property('data'));"
                    ]
                  }
                }
              ],
              "request": {
                "method": "GET",
                "url": {
                  "raw": "{{base_url}}{{api_prefix}}/gpm/repository/{{package_slug}}",
                  "host": [
                    "{{base_url}}{{api_prefix}}"
                  ],
                  "path": [
                    "gpm",
                    "repository",
                    "{{package_slug}}"
                  ]
                },
                "description": "Get details for a specific package in the GPM repository.",
                "header": [
                  {
                    "key": "X-API-Key",
                    "value": "{{api_key}}",
                    "description": "API Key authentication"
                  },
                  {
                    "key": "X-Grav-Environment",
                    "value": "{{grav_environment}}",
                    "description": "Grav environment"
                  }
                ]
              }
            }
          ]
        },
        {
          "name": "GPM / Plugin Pages",
          "description": "Plugin admin page definitions and custom web component scripts.",
          "item": [
            {
              "name": "Plugin Page Definition",
              "event": [
                {
                  "listen": "test",
                  "script": {
                    "type": "text/javascript",
                    "exec": [
                      "const res = pm.response.json();",
                      "pm.test('Status is 200', () => pm.response.to.have.status(200));",
                      "pm.test('Has data', () => pm.expect(res).to.have.property('data'));",
                      "pm.test('Has required fields', () => {",
                      "    const d = res.data;",
                      "    pm.expect(d).to.have.property('id');",
                      "    pm.expect(d).to.have.property('plugin');",
                      "    pm.expect(d).to.have.property('title');",
                      "    pm.expect(d).to.have.property('page_type');",
                      "    pm.expect(d).to.have.property('has_custom_component');",
                      "});"
                    ]
                  }
                }
              ],
              "request": {
                "method": "GET",
                "url": {
                  "raw": "{{base_url}}{{api_prefix}}/gpm/plugins/{{package_slug}}/page",
                  "host": [
                    "{{base_url}}{{api_prefix}}"
                  ],
                  "path": [
                    "gpm",
                    "plugins",
                    "{{package_slug}}",
                    "page"
                  ]
                },
                "description": "Get the admin page definition for a plugin. Returns page metadata, blueprint reference, data/save endpoints, and available actions.",
                "header": [
                  {
                    "key": "X-API-Key",
                    "value": "{{api_key}}",
                    "description": "API Key authentication"
                  },
                  {
                    "key": "X-Grav-Environment",
                    "value": "{{grav_environment}}",
                    "description": "Grav environment"
                  }
                ]
              }
            },
            {
              "name": "Plugin Page Script",
              "event": [
                {
                  "listen": "test",
                  "script": {
                    "type": "text/javascript",
                    "exec": [
                      "pm.test('Status is 200', () => pm.response.to.have.status(200));",
                      "pm.test('Content-Type is JavaScript', () => {",
                      "    pm.expect(pm.response.headers.get('Content-Type')).to.include('application/javascript');",
                      "});"
                    ]
                  }
                }
              ],
              "request": {
                "method": "GET",
                "url": {
                  "raw": "{{base_url}}{{api_prefix}}/gpm/plugins/{{package_slug}}/page-script",
                  "host": [
                    "{{base_url}}{{api_prefix}}"
                  ],
                  "path": [
                    "gpm",
                    "plugins",
                    "{{package_slug}}",
                    "page-script"
                  ]
                },
                "description": "Get the JavaScript web component script for a plugin's custom admin page. Returns application/javascript content.",
                "header": [
                  {
                    "key": "X-API-Key",
                    "value": "{{api_key}}",
                    "description": "API Key authentication"
                  },
                  {
                    "key": "X-Grav-Environment",
                    "value": "{{grav_environment}}",
                    "description": "Grav environment"
                  }
                ]
              }
            },
            {
              "name": "Plugin Report Script",
              "event": [
                {
                  "listen": "test",
                  "script": {
                    "type": "text/javascript",
                    "exec": [
                      "pm.test('Status is 200', () => pm.response.to.have.status(200));",
                      "pm.test('Content-Type is JavaScript', () => {",
                      "    pm.expect(pm.response.headers.get('Content-Type')).to.include('application/javascript');",
                      "});"
                    ]
                  }
                }
              ],
              "request": {
                "method": "GET",
                "url": {
                  "raw": "{{base_url}}{{api_prefix}}/gpm/plugins/{{package_slug}}/report-script/{{report_id}}",
                  "host": [
                    "{{base_url}}{{api_prefix}}"
                  ],
                  "path": [
                    "gpm",
                    "plugins",
                    "{{package_slug}}",
                    "report-script",
                    "{{report_id}}"
                  ]
                },
                "description": "Get the JavaScript web component script for a plugin's report. Returns application/javascript content. The reportId matches the component field from the /reports endpoint response.",
                "header": [
                  {
                    "key": "X-API-Key",
                    "value": "{{api_key}}",
                    "description": "API Key authentication"
                  },
                  {
                    "key": "X-Grav-Environment",
                    "value": "{{grav_environment}}",
                    "description": "Grav environment"
                  }
                ]
              }
            }
          ]
        },
        {
          "id": "openapi:getGravChangelog",
          "name": "Get the Grav core changelog for pending versions",
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "// Generated from openapi.yaml by tests/newman/sync-collection.php; the Newman suite skips it.",
                  "if (pm.variables.get('newman_suite')) { pm.execution.skipRequest(); }"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "X-API-Key",
                "value": "{{api_key}}",
                "description": "API Key authentication"
              },
              {
                "key": "X-Grav-Environment",
                "value": "{{grav_environment}}",
                "description": "Grav environment"
              }
            ],
            "url": {
              "raw": "{{base_url}}{{api_prefix}}/gpm/grav/changelog",
              "host": [
                "{{base_url}}{{api_prefix}}"
              ],
              "path": [
                "gpm",
                "grav",
                "changelog"
              ],
              "query": [
                {
                  "key": "flush",
                  "value": "false",
                  "description": "When `true`, refresh the cached GPM repository data before reading the changelog.",
                  "disabled": true
                }
              ]
            },
            "description": "Returns the Grav core changelog entries for versions newer than the installed `GRAV_VERSION`, assembled from the GPM repository into one Markdown document. Each version gets a `# v{version} ({date})` heading followed by its notes. When the site is already on the latest version, `content` is an empty string.\n\nRequires `api.gpm.read`. Admin Next uses this for the \"What's new\" view before a core upgrade."
          }
        },
        {
          "id": "openapi:getPluginCustomFieldBundle",
          "name": "Get all of a plugin's custom field scripts",
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "// Generated from openapi.yaml by tests/newman/sync-collection.php; the Newman suite skips it.",
                  "if (pm.variables.get('newman_suite')) { pm.execution.skipRequest(); }"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "X-API-Key",
                "value": "{{api_key}}",
                "description": "API Key authentication"
              },
              {
                "key": "X-Grav-Environment",
                "value": "{{grav_environment}}",
                "description": "Grav environment"
              }
            ],
            "url": {
              "raw": "{{base_url}}{{api_prefix}}/gpm/plugins/:slug/fields",
              "host": [
                "{{base_url}}{{api_prefix}}"
              ],
              "path": [
                "gpm",
                "plugins",
                ":slug",
                "fields"
              ],
              "variable": [
                {
                  "key": "slug",
                  "value": "{{package_slug}}",
                  "description": "Installed plugin slug. Must not contain `/`, `\\` or be `.`/`..`."
                }
              ]
            },
            "description": "Returns every Admin Next custom field web component a plugin ships (`admin-next/fields/*.js`) in one response, as a JSON map of field type to JavaScript source. Admin Next fetches this once per plugin instead of one request per field type.\n\nThe response is NOT wrapped in the usual `data` envelope. A plugin with no fields folder returns an empty collection (serialized as `[]`). The plugin does not have to be enabled.\n\nConditional GET is supported: the `ETag` is built from each file's name, mtime and size, and a matching `If-None-Match` returns 304. `Cache-Control` is `private, no-cache`. Requires `api.access`."
          }
        },
        {
          "id": "openapi:getThemeCustomFieldBundle",
          "name": "Get all of a theme's custom field scripts",
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "// Generated from openapi.yaml by tests/newman/sync-collection.php; the Newman suite skips it.",
                  "if (pm.variables.get('newman_suite')) { pm.execution.skipRequest(); }"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "X-API-Key",
                "value": "{{api_key}}",
                "description": "API Key authentication"
              },
              {
                "key": "X-Grav-Environment",
                "value": "{{grav_environment}}",
                "description": "Grav environment"
              }
            ],
            "url": {
              "raw": "{{base_url}}{{api_prefix}}/gpm/themes/:slug/fields",
              "host": [
                "{{base_url}}{{api_prefix}}"
              ],
              "path": [
                "gpm",
                "themes",
                ":slug",
                "fields"
              ],
              "variable": [
                {
                  "key": "slug",
                  "value": "{{package_slug}}",
                  "description": "Installed theme slug. Must not contain `/`, `\\` or be `.`/`..`."
                }
              ]
            },
            "description": "Theme counterpart of `GET /gpm/plugins/{slug}/fields`: returns every Admin Next custom field web component the theme ships (`admin-next/fields/*.js`) as a JSON map of field type to JavaScript source, not wrapped in the `data` envelope. The theme does not have to be active.\n\nConditional GET with a stat-based `ETag` and 304 is supported; `Cache-Control` is `private, no-cache`. Requires `api.access`."
          }
        },
        {
          "id": "openapi:getPluginCustomFieldScript",
          "name": "Get one plugin custom field script",
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "// Generated from openapi.yaml by tests/newman/sync-collection.php; the Newman suite skips it.",
                  "if (pm.variables.get('newman_suite')) { pm.execution.skipRequest(); }"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "X-API-Key",
                "value": "{{api_key}}",
                "description": "API Key authentication"
              },
              {
                "key": "X-Grav-Environment",
                "value": "{{grav_environment}}",
                "description": "Grav environment"
              }
            ],
            "url": {
              "raw": "{{base_url}}{{api_prefix}}/gpm/plugins/:slug/field/:type",
              "host": [
                "{{base_url}}{{api_prefix}}"
              ],
              "path": [
                "gpm",
                "plugins",
                ":slug",
                "field",
                ":type"
              ],
              "variable": [
                {
                  "key": "slug",
                  "value": "{{package_slug}}",
                  "description": "Installed plugin slug. Must not contain `/`, `\\` or be `.`/`..`."
                },
                {
                  "key": "type",
                  "value": "",
                  "description": "Custom field type; the file `admin-next/fields/{type}.js` is served. Only the basename is used."
                }
              ]
            },
            "description": "Serves a single Admin Next custom field web component from the plugin's `admin-next/fields/{type}.js`. Admin Next loads it on demand when a blueprint uses a field type it does not know (see `GET /custom-fields` for the type-to-provider map). The plugin does not have to be enabled, so a disabled plugin's own settings form can still draw its fields.\n\nConditional GET is supported with an `ETag` of the form `\"{mtime-hex}-{size-hex}\"` and a 304 response; `Cache-Control` is `private, no-cache`. Requires `api.access`."
          }
        },
        {
          "id": "openapi:getThemeCustomFieldScript",
          "name": "Get one theme custom field script",
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "// Generated from openapi.yaml by tests/newman/sync-collection.php; the Newman suite skips it.",
                  "if (pm.variables.get('newman_suite')) { pm.execution.skipRequest(); }"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "X-API-Key",
                "value": "{{api_key}}",
                "description": "API Key authentication"
              },
              {
                "key": "X-Grav-Environment",
                "value": "{{grav_environment}}",
                "description": "Grav environment"
              }
            ],
            "url": {
              "raw": "{{base_url}}{{api_prefix}}/gpm/themes/:slug/field/:type",
              "host": [
                "{{base_url}}{{api_prefix}}"
              ],
              "path": [
                "gpm",
                "themes",
                ":slug",
                "field",
                ":type"
              ],
              "variable": [
                {
                  "key": "slug",
                  "value": "{{package_slug}}",
                  "description": "Installed theme slug. Must not contain `/`, `\\` or be `.`/`..`."
                },
                {
                  "key": "type",
                  "value": "",
                  "description": "Custom field type; the file `admin-next/fields/{type}.js` is served. Only the basename is used."
                }
              ]
            },
            "description": "Theme counterpart of `GET /gpm/plugins/{slug}/field/{type}`: serves `admin-next/fields/{type}.js` from the theme folder as JavaScript.\n\nConditional GET with `ETag` and 304 is supported; `Cache-Control` is `private, no-cache`. Requires `api.access`."
          }
        },
        {
          "id": "openapi:getPluginWidgetScript",
          "name": "Get a plugin floating widget script",
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "// Generated from openapi.yaml by tests/newman/sync-collection.php; the Newman suite skips it.",
                  "if (pm.variables.get('newman_suite')) { pm.execution.skipRequest(); }"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "X-API-Key",
                "value": "{{api_key}}",
                "description": "API Key authentication"
              },
              {
                "key": "X-Grav-Environment",
                "value": "{{grav_environment}}",
                "description": "Grav environment"
              }
            ],
            "url": {
              "raw": "{{base_url}}{{api_prefix}}/gpm/plugins/:slug/widget-script",
              "host": [
                "{{base_url}}{{api_prefix}}"
              ],
              "path": [
                "gpm",
                "plugins",
                ":slug",
                "widget-script"
              ],
              "variable": [
                {
                  "key": "slug",
                  "value": "{{package_slug}}",
                  "description": "Installed plugin slug. Must not contain `/`, `\\` or be `.`/`..`."
                }
              ]
            },
            "description": "Serves the floating widget web component a plugin ships at `admin-next/widgets/{slug}.js`. Admin Next loads it for widgets listed by `GET /floating-widgets`. The plugin must be enabled; a disabled plugin's script is a 404.\n\nConditional GET with `ETag` and 304 is supported; `Cache-Control` is `private, no-cache`. Requires `api.access`."
          }
        },
        {
          "id": "openapi:getPluginPanelScript",
          "name": "Get a plugin context panel script",
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "// Generated from openapi.yaml by tests/newman/sync-collection.php; the Newman suite skips it.",
                  "if (pm.variables.get('newman_suite')) { pm.execution.skipRequest(); }"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "X-API-Key",
                "value": "{{api_key}}",
                "description": "API Key authentication"
              },
              {
                "key": "X-Grav-Environment",
                "value": "{{grav_environment}}",
                "description": "Grav environment"
              }
            ],
            "url": {
              "raw": "{{base_url}}{{api_prefix}}/gpm/plugins/:slug/panel-script",
              "host": [
                "{{base_url}}{{api_prefix}}"
              ],
              "path": [
                "gpm",
                "plugins",
                ":slug",
                "panel-script"
              ],
              "variable": [
                {
                  "key": "slug",
                  "value": "{{package_slug}}",
                  "description": "Installed plugin slug. Must not contain `/`, `\\` or be `.`/`..`."
                }
              ]
            },
            "description": "Serves the page-editor context panel web component a plugin ships at `admin-next/panels/{slug}.js`. Admin Next loads it when the user opens a panel listed by `GET /context-panels`. The plugin must be enabled; a disabled plugin's script is a 404.\n\nConditional GET with `ETag` and 304 is supported; `Cache-Control` is `private, no-cache`. Requires `api.access`."
          }
        },
        {
          "id": "openapi:getPluginModalScript",
          "name": "Get a plugin modal script",
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "// Generated from openapi.yaml by tests/newman/sync-collection.php; the Newman suite skips it.",
                  "if (pm.variables.get('newman_suite')) { pm.execution.skipRequest(); }"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "X-API-Key",
                "value": "{{api_key}}",
                "description": "API Key authentication"
              },
              {
                "key": "X-Grav-Environment",
                "value": "{{grav_environment}}",
                "description": "Grav environment"
              }
            ],
            "url": {
              "raw": "{{base_url}}{{api_prefix}}/gpm/plugins/:slug/modal-script/:modalId",
              "host": [
                "{{base_url}}{{api_prefix}}"
              ],
              "path": [
                "gpm",
                "plugins",
                ":slug",
                "modal-script",
                ":modalId"
              ],
              "variable": [
                {
                  "key": "slug",
                  "value": "{{package_slug}}",
                  "description": "Installed plugin slug. Must not contain `/`, `\\` or be `.`/`..`."
                },
                {
                  "key": "modalId",
                  "value": "",
                  "description": "Modal component identifier; the file `admin-next/modals/{modalId}.js` is served. Only the basename is used."
                }
              ]
            },
            "description": "Serves a modal dialog web component a plugin ships at `admin-next/modals/{modalId}.js`. A plugin can ship several modals; Admin Next mounts each as `grav-{slug}--modal-{modalId}` when it is opened through `window.__GRAV_DIALOGS.open()`, a menubar `modal` action, or an editor toolbar button. The plugin must be enabled; a disabled plugin's script is a 404.\n\nConditional GET with `ETag` and 304 is supported; `Cache-Control` is `private, no-cache`. Requires `api.access`."
          }
        }
      ]
    },
    {
      "name": "Scheduler",
      "description": "Scheduler management — list jobs, view status, check history, and run the scheduler.",
      "item": [
        {
          "name": "List Jobs",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "const res = pm.response.json();",
                  "pm.test('Status is 200', () => pm.response.to.have.status(200));",
                  "pm.test('Has data', () => pm.expect(res).to.have.property('data'));"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "url": {
              "raw": "{{base_url}}{{api_prefix}}/scheduler/jobs",
              "host": [
                "{{base_url}}{{api_prefix}}"
              ],
              "path": [
                "scheduler",
                "jobs"
              ]
            },
            "description": "List all configured scheduler jobs.",
            "header": [
              {
                "key": "X-API-Key",
                "value": "{{api_key}}",
                "description": "API Key authentication"
              },
              {
                "key": "X-Grav-Environment",
                "value": "{{grav_environment}}",
                "description": "Grav environment"
              }
            ]
          }
        },
        {
          "name": "Scheduler Status",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "const res = pm.response.json();",
                  "pm.test('Status is 200', () => pm.response.to.have.status(200));",
                  "pm.test('Has data', () => pm.expect(res).to.have.property('data'));"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "url": {
              "raw": "{{base_url}}{{api_prefix}}/scheduler/status",
              "host": [
                "{{base_url}}{{api_prefix}}"
              ],
              "path": [
                "scheduler",
                "status"
              ]
            },
            "description": "Get the current scheduler status and cron configuration.",
            "header": [
              {
                "key": "X-API-Key",
                "value": "{{api_key}}",
                "description": "API Key authentication"
              },
              {
                "key": "X-Grav-Environment",
                "value": "{{grav_environment}}",
                "description": "Grav environment"
              }
            ]
          }
        },
        {
          "name": "Job History",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "const res = pm.response.json();",
                  "pm.test('Status is 200', () => pm.response.to.have.status(200));",
                  "pm.test('Has data', () => pm.expect(res).to.have.property('data'));"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "url": {
              "raw": "{{base_url}}{{api_prefix}}/scheduler/history",
              "host": [
                "{{base_url}}{{api_prefix}}"
              ],
              "path": [
                "scheduler",
                "history"
              ]
            },
            "description": "View the history of scheduler job executions.",
            "header": [
              {
                "key": "X-API-Key",
                "value": "{{api_key}}",
                "description": "API Key authentication"
              },
              {
                "key": "X-Grav-Environment",
                "value": "{{grav_environment}}",
                "description": "Grav environment"
              }
            ]
          }
        },
        {
          "name": "Run Scheduler",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('Status is 200', () => pm.response.to.have.status(200));"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "X-API-Key",
                "value": "{{api_key}}",
                "description": "API Key authentication"
              },
              {
                "key": "X-Grav-Environment",
                "value": "{{grav_environment}}",
                "description": "Grav environment"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n    \"force\": false\n}"
            },
            "url": {
              "raw": "{{base_url}}{{api_prefix}}/scheduler/run",
              "host": [
                "{{base_url}}{{api_prefix}}"
              ],
              "path": [
                "scheduler",
                "run"
              ]
            },
            "description": "Trigger the scheduler to run. Set force to true to run regardless of schedule."
          }
        }
      ]
    },
    {
      "name": "System Info",
      "description": "System info and diagnostics overview.",
      "item": [
        {
          "name": "System Info",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "const res = pm.response.json();",
                  "pm.test('Status is 200', () => pm.response.to.have.status(200));",
                  "pm.test('Has data', () => pm.expect(res).to.have.property('data'));"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "url": {
              "raw": "{{base_url}}{{api_prefix}}/systeminfo",
              "host": [
                "{{base_url}}{{api_prefix}}"
              ],
              "path": [
                "systeminfo"
              ]
            },
            "description": "Get system info overview including environment checks and diagnostics.",
            "header": [
              {
                "key": "X-API-Key",
                "value": "{{api_key}}",
                "description": "API Key authentication"
              },
              {
                "key": "X-Grav-Environment",
                "value": "{{grav_environment}}",
                "description": "Grav environment"
              }
            ]
          }
        }
      ]
    },
    {
      "name": "Reports",
      "description": "Plugin-extensible diagnostic reports.",
      "item": [
        {
          "name": "Get Reports",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "const res = pm.response.json();",
                  "pm.test('Status is 200', () => pm.response.to.have.status(200));",
                  "pm.test('Has data array', () => pm.expect(res.data).to.be.an('array'));"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "url": {
              "raw": "{{base_url}}{{api_prefix}}/reports",
              "host": [
                "{{base_url}}{{api_prefix}}"
              ],
              "path": [
                "reports"
              ]
            },
            "description": "Get plugin-extensible diagnostic reports including security check, YAML linting, and plugin-provided reports.",
            "header": [
              {
                "key": "X-API-Key",
                "value": "{{api_key}}",
                "description": "API Key authentication"
              },
              {
                "key": "X-Grav-Environment",
                "value": "{{grav_environment}}",
                "description": "Grav environment"
              }
            ]
          }
        },
        {
          "id": "openapi:addTwigSandboxAllowlistEntry",
          "name": "Add a blocked token to the Twig sandbox allowlist",
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "// Generated from openapi.yaml by tests/newman/sync-collection.php; the Newman suite skips it.",
                  "if (pm.variables.get('newman_suite')) { pm.execution.skipRequest(); }"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "X-API-Key",
                "value": "{{api_key}}",
                "description": "API Key authentication"
              },
              {
                "key": "X-Grav-Environment",
                "value": "{{grav_environment}}",
                "description": "Grav environment"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url}}{{api_prefix}}/reports/twig-content/allowlist",
              "host": [
                "{{base_url}}{{api_prefix}}"
              ],
              "path": [
                "reports",
                "twig-content",
                "allowlist"
              ]
            },
            "description": "Appends one token to the matching `security.twig_sandbox.allowed_*` list and saves it to `user/config/security.yaml`. The shipped defaults live in code, so the file only holds the site's own additions. The live config is updated, the standard cache is cleared and `onApiConfigUpdated` fires with scope `security`.\n\nFlat rules (`tag`, `filter`, `function`) de-duplicate case-insensitively. Class rules (`method`, `property`) need `class` and append to that class's row (or add a new row).\n\nRing-buffer events matching the rule, token and class are removed, so the report stops listing a block that is now allowed; `resolved` counts them.\n\nSuper admin only. An API key needs `admin.super` in its scopes; demo accounts are rejected.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"rule\": \"filter\",\n  \"token\": \"markdown\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "id": "openapi:clearTwigContentEvents",
          "name": "Clear the Twig in Content event log",
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "// Generated from openapi.yaml by tests/newman/sync-collection.php; the Newman suite skips it.",
                  "if (pm.variables.get('newman_suite')) { pm.execution.skipRequest(); }"
                ]
              }
            }
          ],
          "request": {
            "method": "DELETE",
            "header": [
              {
                "key": "X-API-Key",
                "value": "{{api_key}}",
                "description": "API Key authentication"
              },
              {
                "key": "X-Grav-Environment",
                "value": "{{grav_environment}}",
                "description": "Grav environment"
              }
            ],
            "url": {
              "raw": "{{base_url}}{{api_prefix}}/reports/twig-content/events",
              "host": [
                "{{base_url}}{{api_prefix}}"
              ],
              "path": [
                "reports",
                "twig-content",
                "events"
              ]
            },
            "description": "Empties the ring buffer of recent blocked or blanked content-Twig tokens that the Twig in Content report and the page-editor banner read. `logs/security.log` is untouched.\n\nRequires `api.system.write` (not the report's read permission), so demo accounts cannot clear it."
          }
        },
        {
          "id": "openapi:getTwigContentPageStatus",
          "name": "Get Twig in Content status for one page",
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "// Generated from openapi.yaml by tests/newman/sync-collection.php; the Newman suite skips it.",
                  "if (pm.variables.get('newman_suite')) { pm.execution.skipRequest(); }"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "X-API-Key",
                "value": "{{api_key}}",
                "description": "API Key authentication"
              },
              {
                "key": "X-Grav-Environment",
                "value": "{{grav_environment}}",
                "description": "Grav environment"
              }
            ],
            "url": {
              "raw": "{{base_url}}{{api_prefix}}/reports/twig-content/page?route=",
              "host": [
                "{{base_url}}{{api_prefix}}"
              ],
              "path": [
                "reports",
                "twig-content",
                "page"
              ],
              "query": [
                {
                  "key": "route",
                  "value": "",
                  "description": "Page route, with leading slash.",
                  "disabled": false
                }
              ]
            },
            "description": "Single-page status for the page-editor banner: whether this page would show raw `{{ }}` / `{% %}` markers to visitors, and the recent blocked events recorded for its route. Checks one page rather than scanning the site.\n\n`leak` is null when the page has no Twig markers, uses modular/theme Twig, will render its Twig normally, or cannot be found (a missing page is not an error). `events` only includes buffer entries whose `route` matches the query value exactly.\n\nRequires `api.reports.read`."
          }
        },
        {
          "id": "openapi:scanTwigContentUsage",
          "name": "Scan page content for Twig the sandbox would block",
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "// Generated from openapi.yaml by tests/newman/sync-collection.php; the Newman suite skips it.",
                  "if (pm.variables.get('newman_suite')) { pm.execution.skipRequest(); }"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "X-API-Key",
                "value": "{{api_key}}",
                "description": "API Key authentication"
              },
              {
                "key": "X-Grav-Environment",
                "value": "{{grav_environment}}",
                "description": "Grav environment"
              }
            ],
            "url": {
              "raw": "{{base_url}}{{api_prefix}}/reports/twig-content/scan",
              "host": [
                "{{base_url}}{{api_prefix}}"
              ],
              "path": [
                "reports",
                "twig-content",
                "scan"
              ]
            },
            "description": "Walks every page (skipping modular/theme Twig) and lists the Twig tags, filters and functions used in content that the effective sandbox allowlists do not permit, meaning what content would need before the gate is switched on. It is a lexical approximation; the authoritative signal is the render-time events in the report.\n\nEach list maps a token to the routes that use it. PHP encodes an empty map as `[]`, so a list with no findings arrives as an empty array rather than `{}`.\n\nRequires `api.reports.read`. Cost grows with the number of pages."
          }
        },
        {
          "id": "openapi:getTwigSandboxPolicy",
          "name": "Get the effective Twig sandbox policy",
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "// Generated from openapi.yaml by tests/newman/sync-collection.php; the Newman suite skips it.",
                  "if (pm.variables.get('newman_suite')) { pm.execution.skipRequest(); }"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "X-API-Key",
                "value": "{{api_key}}",
                "description": "API Key authentication"
              },
              {
                "key": "X-Grav-Environment",
                "value": "{{grav_environment}}",
                "description": "Grav environment"
              }
            ],
            "url": {
              "raw": "{{base_url}}{{api_prefix}}/reports/twig-content/sandbox-policy",
              "host": [
                "{{base_url}}{{api_prefix}}"
              ],
              "path": [
                "reports",
                "twig-content",
                "sandbox-policy"
              ]
            },
            "description": "Read-only breakdown of the page-content Twig sandbox: for each list, the built-in defaults from code, the site's additive `allowed_*` entries, its `denied_*` removals, and the effective result. Built from configuration only; additions made by plugins through `onBuildTwigSandboxPolicy` are not included.\n\nRequires `api.reports.read`."
          }
        }
      ]
    },
    {
      "name": "Sidebar",
      "description": "Admin sidebar navigation items contributed by plugins.",
      "item": [
        {
          "name": "Get Sidebar Items",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "const res = pm.response.json();",
                  "pm.test('Status is 200', () => pm.response.to.have.status(200));",
                  "pm.test('Has data array', () => pm.expect(res.data).to.be.an('array'));",
                  "pm.test('Items have required fields', () => {",
                  "    if (res.data.length > 0) {",
                  "        const item = res.data[0];",
                  "        pm.expect(item).to.have.property('id');",
                  "        pm.expect(item).to.have.property('plugin');",
                  "        pm.expect(item).to.have.property('label');",
                  "        pm.expect(item).to.have.property('icon');",
                  "        pm.expect(item).to.have.property('route');",
                  "        pm.expect(item).to.have.property('priority');",
                  "    }",
                  "});"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "url": {
              "raw": "{{base_url}}{{api_prefix}}/sidebar/items",
              "host": [
                "{{base_url}}{{api_prefix}}"
              ],
              "path": [
                "sidebar",
                "items"
              ]
            },
            "description": "Get all admin sidebar navigation items contributed by installed plugins.",
            "header": [
              {
                "key": "X-API-Key",
                "value": "{{api_key}}",
                "description": "API Key authentication"
              },
              {
                "key": "X-Grav-Environment",
                "value": "{{grav_environment}}",
                "description": "Grav environment"
              }
            ]
          }
        }
      ]
    },
    {
      "name": "Dashboard",
      "description": "Dashboard data — notifications, news feed, and statistics.",
      "item": [
        {
          "name": "Get Notifications",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "const res = pm.response.json();",
                  "pm.test('Status is 200', () => pm.response.to.have.status(200));",
                  "pm.test('Has data', () => pm.expect(res).to.have.property('data'));"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "url": {
              "raw": "{{base_url}}{{api_prefix}}/dashboard/notifications",
              "host": [
                "{{base_url}}{{api_prefix}}"
              ],
              "path": [
                "dashboard",
                "notifications"
              ]
            },
            "description": "Get all dashboard notifications.",
            "header": [
              {
                "key": "X-API-Key",
                "value": "{{api_key}}",
                "description": "API Key authentication"
              },
              {
                "key": "X-Grav-Environment",
                "value": "{{grav_environment}}",
                "description": "Grav environment"
              }
            ]
          }
        },
        {
          "name": "Hide Notification",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('Status is 200', () => pm.response.to.have.status(200));"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "X-API-Key",
                "value": "{{api_key}}",
                "description": "API Key authentication"
              },
              {
                "key": "X-Grav-Environment",
                "value": "{{grav_environment}}",
                "description": "Grav environment"
              }
            ],
            "url": {
              "raw": "{{base_url}}{{api_prefix}}/dashboard/notifications/{{notification_id}}/hide",
              "host": [
                "{{base_url}}{{api_prefix}}"
              ],
              "path": [
                "dashboard",
                "notifications",
                "{{notification_id}}",
                "hide"
              ]
            },
            "description": "Hide a specific notification by ID."
          }
        },
        {
          "name": "News Feed",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "const res = pm.response.json();",
                  "pm.test('Status is 200', () => pm.response.to.have.status(200));",
                  "pm.test('Has data', () => pm.expect(res).to.have.property('data'));"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "url": {
              "raw": "{{base_url}}{{api_prefix}}/dashboard/feed",
              "host": [
                "{{base_url}}{{api_prefix}}"
              ],
              "path": [
                "dashboard",
                "feed"
              ]
            },
            "description": "Get the dashboard news feed.",
            "header": [
              {
                "key": "X-API-Key",
                "value": "{{api_key}}",
                "description": "API Key authentication"
              },
              {
                "key": "X-Grav-Environment",
                "value": "{{grav_environment}}",
                "description": "Grav environment"
              }
            ]
          }
        },
        {
          "name": "Dashboard Stats",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "const res = pm.response.json();",
                  "pm.test('Status is 200', () => pm.response.to.have.status(200));",
                  "pm.test('Has data', () => pm.expect(res).to.have.property('data'));"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "url": {
              "raw": "{{base_url}}{{api_prefix}}/dashboard/stats",
              "host": [
                "{{base_url}}{{api_prefix}}"
              ],
              "path": [
                "dashboard",
                "stats"
              ]
            },
            "description": "Get dashboard statistics (page counts, user counts, etc.).",
            "header": [
              {
                "key": "X-API-Key",
                "value": "{{api_key}}",
                "description": "API Key authentication"
              },
              {
                "key": "X-Grav-Environment",
                "value": "{{grav_environment}}",
                "description": "Grav environment"
              }
            ]
          }
        },
        {
          "name": "Dashboard Popularity",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "const res = pm.response.json();",
                  "pm.test('Status is 200', () => pm.response.to.have.status(200));",
                  "pm.test('Has summary', () => pm.expect(res.data).to.have.property('summary'));"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "url": {
              "raw": "{{base_url}}{{api_prefix}}/dashboard/popularity",
              "host": [
                "{{base_url}}{{api_prefix}}"
              ],
              "path": [
                "dashboard",
                "popularity"
              ]
            },
            "description": "Get page view statistics including daily chart data (last 14 days), summary counters, and top pages by views.",
            "header": [
              {
                "key": "X-API-Key",
                "value": "{{api_key}}",
                "description": "API Key authentication"
              },
              {
                "key": "X-Grav-Environment",
                "value": "{{grav_environment}}",
                "description": "Grav environment"
              }
            ]
          }
        },
        {
          "id": "openapi:getDashboardWidgets",
          "name": "Get the resolved dashboard widget list",
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "// Generated from openapi.yaml by tests/newman/sync-collection.php; the Newman suite skips it.",
                  "if (pm.variables.get('newman_suite')) { pm.execution.skipRequest(); }"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "X-API-Key",
                "value": "{{api_key}}",
                "description": "API Key authentication"
              },
              {
                "key": "X-Grav-Environment",
                "value": "{{grav_environment}}",
                "description": "Grav environment"
              }
            ],
            "url": {
              "raw": "{{base_url}}{{api_prefix}}/dashboard/widgets",
              "host": [
                "{{base_url}}{{api_prefix}}"
              ],
              "path": [
                "dashboard",
                "widgets"
              ]
            },
            "description": "Returns the widgets for the Admin Next dashboard, merged from the built-in core registry, plugin contributions (`onApiDashboardWidgets`), the site default layout, and the current user's layout. Widgets whose `authorize` permission the user lacks are left out (super-admins within their API-key scope see every widget), and widgets the site layout hides are removed entirely so a user cannot re-enable them. Each widget is annotated with its effective `visible`, `size` and `order`, and the list is sorted by `order`.\n\nRequires `api.access`. This endpoint exists to support Admin Next."
          }
        },
        {
          "id": "openapi:updateDashboardUserLayout",
          "name": "Save the current user's dashboard layout",
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "// Generated from openapi.yaml by tests/newman/sync-collection.php; the Newman suite skips it.",
                  "if (pm.variables.get('newman_suite')) { pm.execution.skipRequest(); }"
                ]
              }
            }
          ],
          "request": {
            "method": "PATCH",
            "header": [
              {
                "key": "X-API-Key",
                "value": "{{api_key}}",
                "description": "API Key authentication"
              },
              {
                "key": "X-Grav-Environment",
                "value": "{{grav_environment}}",
                "description": "Grav environment"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url}}{{api_prefix}}/dashboard/layout",
              "host": [
                "{{base_url}}{{api_prefix}}"
              ],
              "path": [
                "dashboard",
                "layout"
              ]
            },
            "description": "Saves the calling user's dashboard layout to `admin_next.dashboard` in their account YAML. Despite the PATCH verb the stored layout is replaced as a whole: send every widget entry you want kept. Entries without a string `id` are dropped, `size` values outside `xs`-`xl` are stored as `null`, and an entry without a numeric `order` is stored without one, so that widget keeps its priority-based position.\n\nRequires `api.access`. Demo accounts are refused. Returns the resolved widget list, the same as `GET /dashboard/widgets`.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"preset\": \"custom\",\n  \"widgets\": [\n    {\n      \"id\": \"core.stats\",\n      \"visible\": true,\n      \"size\": \"lg\",\n      \"order\": 10\n    },\n    {\n      \"id\": \"core.news-feed\",\n      \"visible\": false,\n      \"order\": 20\n    }\n  ]\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "id": "openapi:updateDashboardSiteLayout",
          "name": "Save the site-wide default dashboard layout",
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "// Generated from openapi.yaml by tests/newman/sync-collection.php; the Newman suite skips it.",
                  "if (pm.variables.get('newman_suite')) { pm.execution.skipRequest(); }"
                ]
              }
            }
          ],
          "request": {
            "method": "PATCH",
            "header": [
              {
                "key": "X-API-Key",
                "value": "{{api_key}}",
                "description": "API Key authentication"
              },
              {
                "key": "X-Grav-Environment",
                "value": "{{grav_environment}}",
                "description": "Grav environment"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url}}{{api_prefix}}/dashboard/site-layout",
              "host": [
                "{{base_url}}{{api_prefix}}"
              ],
              "path": [
                "dashboard",
                "site-layout"
              ]
            },
            "description": "Saves the default dashboard layout for every user to `dashboard.site_layout` in `user/config/admin-next.yaml`. The stored layout is replaced as a whole. A widget marked `visible: false` here is hidden for all users and cannot be re-enabled per user; sizes and orders act as defaults that user layouts can override.\n\nRequires super-admin (`admin.super`, subject to the API-key scope cap). Demo accounts are refused. Returns the resolved widget list for the caller.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"preset\": \"custom\",\n  \"widgets\": [\n    {\n      \"id\": \"core.stats\",\n      \"visible\": true,\n      \"size\": \"xs\",\n      \"order\": 0\n    }\n  ]\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "id": "openapi:getDashboardExposureProbe",
          "name": "Get storage exposure probe URLs",
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "// Generated from openapi.yaml by tests/newman/sync-collection.php; the Newman suite skips it.",
                  "if (pm.variables.get('newman_suite')) { pm.execution.skipRequest(); }"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "X-API-Key",
                "value": "{{api_key}}",
                "description": "API Key authentication"
              },
              {
                "key": "X-Grav-Environment",
                "value": "{{grav_environment}}",
                "description": "Grav environment"
              }
            ],
            "url": {
              "raw": "{{base_url}}{{api_prefix}}/dashboard/security/exposure-probe",
              "host": [
                "{{base_url}}{{api_prefix}}"
              ],
              "path": [
                "dashboard",
                "security",
                "exposure-probe"
              ]
            },
            "description": "Supports the Admin Next security check that tests whether private storage folders are reachable over HTTP. For each of `user://data`, `backup://` and `tmp://` that lives inside the web root, the server makes sure a sentinel file `grav-security-probe.{dat,txt,zip,json}` exists (creating it with a random hex token if missing, never overwriting an existing file or following a symlink) and returns its public URL and token.\n\nThe browser then requests each URL through the site's real web server: if the response body matches the token, that folder and extension are publicly exposed. Folders relocated outside the web root are skipped. The top-level `url`, `token` and `available` fields repeat the first `user://data` probe for older Admin Next builds.\n\nNote that this GET creates files on first call. Requires `api.system.read`."
          }
        }
      ]
    },
    {
      "name": "Webhooks",
      "description": "Webhook management — create, update, delete, and test webhooks.",
      "item": [
        {
          "name": "List Webhooks",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "const res = pm.response.json();",
                  "pm.test('Status is 200', () => pm.response.to.have.status(200));",
                  "pm.test('Has data', () => pm.expect(res).to.have.property('data'));"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "url": {
              "raw": "{{base_url}}{{api_prefix}}/webhooks",
              "host": [
                "{{base_url}}{{api_prefix}}"
              ],
              "path": [
                "webhooks"
              ]
            },
            "description": "List all configured webhooks.",
            "header": [
              {
                "key": "X-API-Key",
                "value": "{{api_key}}",
                "description": "API Key authentication"
              },
              {
                "key": "X-Grav-Environment",
                "value": "{{grav_environment}}",
                "description": "Grav environment"
              }
            ]
          }
        },
        {
          "name": "Create Webhook",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "const res = pm.response.json();",
                  "pm.test('Status is 201', () => pm.response.to.have.status(201));",
                  "",
                  "if (res.data && res.data.id) {",
                  "    pm.collectionVariables.set('webhook_id', res.data.id);",
                  "}"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "X-API-Key",
                "value": "{{api_key}}",
                "description": "API Key authentication"
              },
              {
                "key": "X-Grav-Environment",
                "value": "{{grav_environment}}",
                "description": "Grav environment"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n    \"url\": \"https://example.com/webhook\",\n    \"events\": [\"page.created\", \"page.updated\"],\n    \"enabled\": true\n}"
            },
            "url": {
              "raw": "{{base_url}}{{api_prefix}}/webhooks",
              "host": [
                "{{base_url}}{{api_prefix}}"
              ],
              "path": [
                "webhooks"
              ]
            },
            "description": "Create a new webhook. The webhook ID is automatically saved to the `webhook_id` variable."
          }
        },
        {
          "name": "Get Webhook",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "const res = pm.response.json();",
                  "pm.test('Status is 200', () => pm.response.to.have.status(200));",
                  "pm.test('Has data', () => pm.expect(res).to.have.property('data'));"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "url": {
              "raw": "{{base_url}}{{api_prefix}}/webhooks/{{webhook_id}}",
              "host": [
                "{{base_url}}{{api_prefix}}"
              ],
              "path": [
                "webhooks",
                "{{webhook_id}}"
              ]
            },
            "description": "Get details for a specific webhook.",
            "header": [
              {
                "key": "X-API-Key",
                "value": "{{api_key}}",
                "description": "API Key authentication"
              },
              {
                "key": "X-Grav-Environment",
                "value": "{{grav_environment}}",
                "description": "Grav environment"
              }
            ]
          }
        },
        {
          "name": "Update Webhook",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('Status is 200', () => pm.response.to.have.status(200));"
                ]
              }
            }
          ],
          "request": {
            "method": "PATCH",
            "header": [
              {
                "key": "X-API-Key",
                "value": "{{api_key}}",
                "description": "API Key authentication"
              },
              {
                "key": "X-Grav-Environment",
                "value": "{{grav_environment}}",
                "description": "Grav environment"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n    \"events\": [\"page.created\", \"page.updated\", \"page.deleted\"]\n}"
            },
            "url": {
              "raw": "{{base_url}}{{api_prefix}}/webhooks/{{webhook_id}}",
              "host": [
                "{{base_url}}{{api_prefix}}"
              ],
              "path": [
                "webhooks",
                "{{webhook_id}}"
              ]
            },
            "description": "Update an existing webhook configuration."
          }
        },
        {
          "name": "Delete Webhook",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('Status is 204', () => pm.response.to.have.status(204));"
                ]
              }
            }
          ],
          "request": {
            "method": "DELETE",
            "url": {
              "raw": "{{base_url}}{{api_prefix}}/webhooks/{{webhook_id}}",
              "host": [
                "{{base_url}}{{api_prefix}}"
              ],
              "path": [
                "webhooks",
                "{{webhook_id}}"
              ]
            },
            "description": "Delete a webhook.",
            "header": [
              {
                "key": "X-API-Key",
                "value": "{{api_key}}",
                "description": "API Key authentication"
              },
              {
                "key": "X-Grav-Environment",
                "value": "{{grav_environment}}",
                "description": "Grav environment"
              }
            ]
          }
        },
        {
          "name": "Webhook Deliveries",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "const res = pm.response.json();",
                  "pm.test('Status is 200', () => pm.response.to.have.status(200));",
                  "pm.test('Has data', () => pm.expect(res).to.have.property('data'));"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "url": {
              "raw": "{{base_url}}{{api_prefix}}/webhooks/{{webhook_id}}/deliveries",
              "host": [
                "{{base_url}}{{api_prefix}}"
              ],
              "path": [
                "webhooks",
                "{{webhook_id}}",
                "deliveries"
              ]
            },
            "description": "View delivery history for a specific webhook.",
            "header": [
              {
                "key": "X-API-Key",
                "value": "{{api_key}}",
                "description": "API Key authentication"
              },
              {
                "key": "X-Grav-Environment",
                "value": "{{grav_environment}}",
                "description": "Grav environment"
              }
            ]
          }
        },
        {
          "name": "Test Webhook",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('Status is 200', () => pm.response.to.have.status(200));"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "X-API-Key",
                "value": "{{api_key}}",
                "description": "API Key authentication"
              },
              {
                "key": "X-Grav-Environment",
                "value": "{{grav_environment}}",
                "description": "Grav environment"
              }
            ],
            "url": {
              "raw": "{{base_url}}{{api_prefix}}/webhooks/{{webhook_id}}/test",
              "host": [
                "{{base_url}}{{api_prefix}}"
              ],
              "path": [
                "webhooks",
                "{{webhook_id}}",
                "test"
              ]
            },
            "description": "Send a test payload to a webhook endpoint."
          }
        }
      ]
    },
    {
      "name": "Workflows",
      "description": "Multi-step workflow examples that chain API calls together.",
      "item": [
        {
          "name": "01. Create Blog Post with Media",
          "item": [
            {
              "name": "1. Create the page",
              "event": [
                {
                  "listen": "test",
                  "script": {
                    "type": "text/javascript",
                    "exec": [
                      "pm.test('Status is 201', () => pm.response.to.have.status(201));"
                    ]
                  }
                }
              ],
              "request": {
                "method": "POST",
                "header": [
                  {
                    "key": "X-API-Key",
                    "value": "{{api_key}}",
                    "description": "API Key authentication"
                  },
                  {
                    "key": "X-Grav-Environment",
                    "value": "{{grav_environment}}",
                    "description": "Grav environment"
                  },
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "body": {
                  "mode": "raw",
                  "raw": "{\n    \"route\": \"/blog/api-workflow-test\",\n    \"title\": \"API Workflow Test Post\",\n    \"template\": \"post\",\n    \"content\": \"# Workflow Test\\n\\nThis post was created as part of an API workflow test.\\n\\n![Hero Image](hero.jpg)\",\n    \"header\": {\n        \"taxonomy\": {\n            \"category\": [\"blog\"],\n            \"tag\": [\"api\", \"test\"]\n        },\n        \"published\": true\n    }\n}"
                },
                "url": {
                  "raw": "{{base_url}}{{api_prefix}}/pages",
                  "host": [
                    "{{base_url}}{{api_prefix}}"
                  ],
                  "path": [
                    "pages"
                  ]
                }
              }
            },
            {
              "name": "2. Upload hero image",
              "request": {
                "method": "POST",
                "body": {
                  "mode": "formdata",
                  "formdata": [
                    {
                      "key": "file",
                      "type": "file",
                      "src": "",
                      "description": "Select hero.jpg"
                    }
                  ]
                },
                "url": {
                  "raw": "{{base_url}}{{api_prefix}}/pages/blog/api-workflow-test/media",
                  "host": [
                    "{{base_url}}{{api_prefix}}"
                  ],
                  "path": [
                    "pages",
                    "blog",
                    "api-workflow-test",
                    "media"
                  ]
                },
                "header": [
                  {
                    "key": "X-API-Key",
                    "value": "{{api_key}}",
                    "description": "API Key authentication"
                  },
                  {
                    "key": "X-Grav-Environment",
                    "value": "{{grav_environment}}",
                    "description": "Grav environment"
                  }
                ]
              }
            },
            {
              "name": "3. Verify the page",
              "request": {
                "method": "GET",
                "url": {
                  "raw": "{{base_url}}{{api_prefix}}/pages/blog/api-workflow-test?render=true",
                  "host": [
                    "{{base_url}}{{api_prefix}}"
                  ],
                  "path": [
                    "pages",
                    "blog",
                    "api-workflow-test"
                  ],
                  "query": [
                    {
                      "key": "render",
                      "value": "true"
                    }
                  ]
                },
                "header": [
                  {
                    "key": "X-API-Key",
                    "value": "{{api_key}}",
                    "description": "API Key authentication"
                  },
                  {
                    "key": "X-Grav-Environment",
                    "value": "{{grav_environment}}",
                    "description": "Grav environment"
                  }
                ]
              }
            },
            {
              "name": "4. Cleanup - delete the page",
              "request": {
                "method": "DELETE",
                "url": {
                  "raw": "{{base_url}}{{api_prefix}}/pages/blog/api-workflow-test?children=true",
                  "host": [
                    "{{base_url}}{{api_prefix}}"
                  ],
                  "path": [
                    "pages",
                    "blog",
                    "api-workflow-test"
                  ],
                  "query": [
                    {
                      "key": "children",
                      "value": "true"
                    }
                  ]
                },
                "header": [
                  {
                    "key": "X-API-Key",
                    "value": "{{api_key}}",
                    "description": "API Key authentication"
                  },
                  {
                    "key": "X-Grav-Environment",
                    "value": "{{grav_environment}}",
                    "description": "Grav environment"
                  }
                ]
              }
            }
          ]
        },
        {
          "name": "02. Full User Lifecycle",
          "item": [
            {
              "name": "1. Create user",
              "request": {
                "method": "POST",
                "header": [
                  {
                    "key": "X-API-Key",
                    "value": "{{api_key}}",
                    "description": "API Key authentication"
                  },
                  {
                    "key": "X-Grav-Environment",
                    "value": "{{grav_environment}}",
                    "description": "Grav environment"
                  },
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "body": {
                  "mode": "raw",
                  "raw": "{\n    \"username\": \"workflow-user\",\n    \"password\": \"T3stP@ss!\",\n    \"email\": \"workflow@example.com\",\n    \"fullname\": \"Workflow User\",\n    \"access\": {\n        \"api\": { \"access\": true, \"pages\": { \"read\": true } }\n    }\n}"
                },
                "url": {
                  "raw": "{{base_url}}{{api_prefix}}/users",
                  "host": [
                    "{{base_url}}{{api_prefix}}"
                  ],
                  "path": [
                    "users"
                  ]
                }
              }
            },
            {
              "name": "2. Generate API key for user",
              "request": {
                "method": "POST",
                "header": [
                  {
                    "key": "X-API-Key",
                    "value": "{{api_key}}",
                    "description": "API Key authentication"
                  },
                  {
                    "key": "X-Grav-Environment",
                    "value": "{{grav_environment}}",
                    "description": "Grav environment"
                  },
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "body": {
                  "mode": "raw",
                  "raw": "{\n    \"name\": \"Workflow Test Key\",\n    \"scopes\": [\"api.pages.read\"]\n}"
                },
                "url": {
                  "raw": "{{base_url}}{{api_prefix}}/users/workflow-user/api-keys",
                  "host": [
                    "{{base_url}}{{api_prefix}}"
                  ],
                  "path": [
                    "users",
                    "workflow-user",
                    "api-keys"
                  ]
                }
              }
            },
            {
              "name": "3. Login as user (get JWT)",
              "event": [
                {
                  "listen": "test",
                  "script": {
                    "type": "text/javascript",
                    "exec": [
                      "const res = pm.response.json();",
                      "if (res.data) {",
                      "    pm.collectionVariables.set('access_token', res.data.access_token);",
                      "    pm.collectionVariables.set('refresh_token', res.data.refresh_token);",
                      "}"
                    ]
                  }
                }
              ],
              "request": {
                "auth": {
                  "type": "noauth"
                },
                "method": "POST",
                "header": [
                  {
                    "key": "X-Grav-Environment",
                    "value": "{{grav_environment}}",
                    "description": "Grav environment"
                  },
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "body": {
                  "mode": "raw",
                  "raw": "{\n    \"username\": \"workflow-user\",\n    \"password\": \"T3stP@ss!\"\n}"
                },
                "url": {
                  "raw": "{{base_url}}{{api_prefix}}/auth/token",
                  "host": [
                    "{{base_url}}{{api_prefix}}"
                  ],
                  "path": [
                    "auth",
                    "token"
                  ]
                }
              }
            },
            {
              "name": "4. List API keys",
              "request": {
                "method": "GET",
                "url": {
                  "raw": "{{base_url}}{{api_prefix}}/users/workflow-user/api-keys",
                  "host": [
                    "{{base_url}}{{api_prefix}}"
                  ],
                  "path": [
                    "users",
                    "workflow-user",
                    "api-keys"
                  ]
                },
                "header": [
                  {
                    "key": "X-API-Key",
                    "value": "{{api_key}}",
                    "description": "API Key authentication"
                  },
                  {
                    "key": "X-Grav-Environment",
                    "value": "{{grav_environment}}",
                    "description": "Grav environment"
                  }
                ]
              }
            },
            {
              "name": "5. Cleanup - delete user",
              "request": {
                "method": "DELETE",
                "url": {
                  "raw": "{{base_url}}{{api_prefix}}/users/workflow-user",
                  "host": [
                    "{{base_url}}{{api_prefix}}"
                  ],
                  "path": [
                    "users",
                    "workflow-user"
                  ]
                },
                "header": [
                  {
                    "key": "X-API-Key",
                    "value": "{{api_key}}",
                    "description": "API Key authentication"
                  },
                  {
                    "key": "X-Grav-Environment",
                    "value": "{{grav_environment}}",
                    "description": "Grav environment"
                  }
                ]
              }
            }
          ]
        }
      ]
    },
    {
      "name": "Blueprints",
      "item": [
        {
          "name": "List Page Types",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('Status is 200', () => pm.response.to.have.status(200));",
                  "pm.test('Returns array', () => pm.expect(pm.response.json().data).to.be.an('array'));",
                  "pm.test('Items have type and label', () => {",
                  "    const items = pm.response.json().data;",
                  "    if (items.length > 0) {",
                  "        pm.expect(items[0]).to.have.property('type');",
                  "        pm.expect(items[0]).to.have.property('label');",
                  "    }",
                  "});"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "X-API-Key",
                "value": "{{api_key}}"
              },
              {
                "key": "X-Grav-Environment",
                "value": "{{grav_environment}}"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url}}{{api_prefix}}/blueprints/pages",
              "host": [
                "{{base_url}}{{api_prefix}}/blueprints/pages"
              ]
            },
            "description": "List all available page template types in the active theme."
          },
          "response": []
        },
        {
          "name": "Get Page Blueprint (default)",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('Status is 200', () => pm.response.to.have.status(200));",
                  "pm.test('Has name', () => pm.expect(pm.response.json().data.name).to.equal('default'));",
                  "pm.test('Has fields', () => pm.expect(pm.response.json().data.fields).to.be.an('array'));",
                  "pm.test('Has validation', () => pm.expect(pm.response.json().data).to.have.property('validation'));"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "X-API-Key",
                "value": "{{api_key}}"
              },
              {
                "key": "X-Grav-Environment",
                "value": "{{grav_environment}}"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url}}{{api_prefix}}/blueprints/pages/default",
              "host": [
                "{{base_url}}{{api_prefix}}/blueprints/pages/default"
              ]
            },
            "description": "Get the resolved blueprint for the default page template. Includes all inherited fields from extends@ and import@ directives."
          },
          "response": []
        },
        {
          "name": "Get Page Blueprint (blog)",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('Status is 200', () => pm.response.to.have.status(200));",
                  "pm.test('Has blog-specific fields', () => {",
                  "    const data = pm.response.json().data;",
                  "    pm.expect(data.fields).to.be.an('array');",
                  "});"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "X-API-Key",
                "value": "{{api_key}}"
              },
              {
                "key": "X-Grav-Environment",
                "value": "{{grav_environment}}"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url}}{{api_prefix}}/blueprints/pages/blog",
              "host": [
                "{{base_url}}{{api_prefix}}/blueprints/pages/blog"
              ]
            },
            "description": "Get the resolved blueprint for the blog page template."
          },
          "response": []
        },
        {
          "name": "Get Plugin Blueprint",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('Status is 200', () => pm.response.to.have.status(200));",
                  "pm.test('Has fields', () => pm.expect(pm.response.json().data.fields).to.be.an('array'));",
                  "pm.test('Has name', () => pm.expect(pm.response.json().data.name).to.equal('email'));"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "X-API-Key",
                "value": "{{api_key}}"
              },
              {
                "key": "X-Grav-Environment",
                "value": "{{grav_environment}}"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url}}{{api_prefix}}/blueprints/plugins/email",
              "host": [
                "{{base_url}}{{api_prefix}}/blueprints/plugins/email"
              ]
            },
            "description": "Get the configuration blueprint for the email plugin."
          },
          "response": []
        },
        {
          "name": "Get Theme Blueprint",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('Status is 200', () => pm.response.to.have.status(200));",
                  "pm.test('Has fields', () => pm.expect(pm.response.json().data.fields).to.be.an('array'));"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "X-API-Key",
                "value": "{{api_key}}"
              },
              {
                "key": "X-Grav-Environment",
                "value": "{{grav_environment}}"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url}}{{api_prefix}}/blueprints/themes/quark",
              "host": [
                "{{base_url}}{{api_prefix}}/blueprints/themes/quark"
              ]
            },
            "description": "Get the configuration blueprint for the quark theme."
          },
          "response": []
        },
        {
          "name": "Get Config Blueprint (system)",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('Status is 200', () => pm.response.to.have.status(200));",
                  "pm.test('Has fields', () => pm.expect(pm.response.json().data.fields).to.be.an('array'));",
                  "pm.test('Has name', () => pm.expect(pm.response.json().data.name).to.equal('system'));"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "X-API-Key",
                "value": "{{api_key}}"
              },
              {
                "key": "X-Grav-Environment",
                "value": "{{grav_environment}}"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url}}{{api_prefix}}/blueprints/config/system",
              "host": [
                "{{base_url}}{{api_prefix}}/blueprints/config/system"
              ]
            },
            "description": "Get the blueprint for system configuration."
          },
          "response": []
        },
        {
          "name": "Get User Blueprint",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "X-API-Key",
                "value": "{{api_key}}"
              },
              {
                "key": "X-Grav-Environment",
                "value": "{{grav_environment}}"
              },
              {
                "key": "Accept",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url}}{{api_prefix}}/blueprints/users",
              "host": [
                "{{base_url}}{{api_prefix}}"
              ],
              "path": [
                "blueprints",
                "users"
              ]
            },
            "description": "Get the user account form blueprint."
          }
        },
        {
          "name": "Get Permissions Tree",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "X-API-Key",
                "value": "{{api_key}}"
              },
              {
                "key": "X-Grav-Environment",
                "value": "{{grav_environment}}"
              },
              {
                "key": "Accept",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url}}{{api_prefix}}/blueprints/users/permissions",
              "host": [
                "{{base_url}}{{api_prefix}}"
              ],
              "path": [
                "blueprints",
                "users",
                "permissions"
              ]
            },
            "description": "Get all registered permission actions as a nested tree."
          }
        },
        {
          "name": "Get Plugin Page Blueprint",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('Status is 200', () => pm.response.to.have.status(200));",
                  "pm.test('Has fields', () => pm.expect(pm.response.json().data.fields).to.be.an('array'));",
                  "pm.test('Has name', () => pm.expect(pm.response.json().data).to.have.property('name'));"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "X-API-Key",
                "value": "{{api_key}}"
              },
              {
                "key": "X-Grav-Environment",
                "value": "{{grav_environment}}"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url}}{{api_prefix}}/blueprints/plugins/api/pages/settings",
              "host": [
                "{{base_url}}{{api_prefix}}"
              ],
              "path": [
                "blueprints",
                "plugins",
                "api",
                "pages",
                "settings"
              ]
            },
            "description": "Get the resolved blueprint for a plugin's custom admin page. Used to render blueprint-form type pages registered by plugins."
          },
          "response": []
        },
        {
          "name": "Resolve Data Options",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "X-API-Key",
                "value": "{{api_key}}"
              },
              {
                "key": "X-Grav-Environment",
                "value": "{{grav_environment}}"
              },
              {
                "key": "Accept",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url}}{{api_prefix}}/data/resolve",
              "host": [
                "{{base_url}}{{api_prefix}}"
              ],
              "path": [
                "data",
                "resolve"
              ]
            },
            "description": "Resolve a blueprint data-options@ directive. Pass ?callable=\\\\Grav\\\\Common\\\\Page\\\\Pages::pageTypes"
          }
        },
        {
          "id": "openapi:getAccountsConfigBlueprint",
          "name": "Get the accounts configuration blueprint",
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "// Generated from openapi.yaml by tests/newman/sync-collection.php; the Newman suite skips it.",
                  "if (pm.variables.get('newman_suite')) { pm.execution.skipRequest(); }"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "X-API-Key",
                "value": "{{api_key}}",
                "description": "API Key authentication"
              },
              {
                "key": "X-Grav-Environment",
                "value": "{{grav_environment}}",
                "description": "Grav environment"
              }
            ],
            "url": {
              "raw": "{{base_url}}{{api_prefix}}/blueprints/config/accounts",
              "host": [
                "{{base_url}}{{api_prefix}}"
              ],
              "path": [
                "blueprints",
                "config",
                "accounts"
              ]
            },
            "description": "Returns the Flex `user-accounts` directory blueprint, the form behind the Users \"Configuration\" tab (edited through `/config/accounts`). Requires `api.config.read`. Plugins can adjust the fields through the `onApiBlueprintResolved` event (context `config`, template `accounts`).\nReturns 404 when the Flex Objects plugin or the `user-accounts` directory is not available."
          }
        },
        {
          "id": "openapi:getGroupBlueprint",
          "name": "Get the group editing blueprint",
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "// Generated from openapi.yaml by tests/newman/sync-collection.php; the Newman suite skips it.",
                  "if (pm.variables.get('newman_suite')) { pm.execution.skipRequest(); }"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "X-API-Key",
                "value": "{{api_key}}",
                "description": "API Key authentication"
              },
              {
                "key": "X-Grav-Environment",
                "value": "{{grav_environment}}",
                "description": "Grav environment"
              }
            ],
            "url": {
              "raw": "{{base_url}}{{api_prefix}}/blueprints/groups",
              "host": [
                "{{base_url}}{{api_prefix}}"
              ],
              "path": [
                "blueprints",
                "groups"
              ]
            },
            "description": "Returns the resolved `user/group.yaml` blueprint used to edit an existing group. Requires `api.users.read`. Plugins can adjust the fields through the `onApiBlueprintResolved` event (context `group`)."
          }
        },
        {
          "id": "openapi:getGroupNewBlueprint",
          "name": "Get the new-group blueprint",
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "// Generated from openapi.yaml by tests/newman/sync-collection.php; the Newman suite skips it.",
                  "if (pm.variables.get('newman_suite')) { pm.execution.skipRequest(); }"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "X-API-Key",
                "value": "{{api_key}}",
                "description": "API Key authentication"
              },
              {
                "key": "X-Grav-Environment",
                "value": "{{grav_environment}}",
                "description": "Grav environment"
              }
            ],
            "url": {
              "raw": "{{base_url}}{{api_prefix}}/blueprints/groups/new",
              "host": [
                "{{base_url}}{{api_prefix}}"
              ],
              "path": [
                "blueprints",
                "groups",
                "new"
              ]
            },
            "description": "Returns the resolved `user/group_new.yaml` blueprint used by the create-group form. Requires `api.users.read`. Plugins can adjust the fields through the `onApiBlueprintResolved` event (context `group_new`)."
          }
        }
      ]
    },
    {
      "name": "Translations",
      "item": [
        {
          "name": "Get All Translations (English)",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('Status is 200', () => pm.response.to.have.status(200));",
                  "pm.test('Has lang', () => pm.expect(pm.response.json().data.lang).to.equal('en'));",
                  "pm.test('Has strings object', () => pm.expect(pm.response.json().data.strings).to.be.an('object'));",
                  "pm.test('Has checksum', () => pm.expect(pm.response.json().data.checksum).to.be.a('string'));",
                  "pm.test('Count matches', () => {",
                  "    const data = pm.response.json().data;",
                  "    pm.expect(data.count).to.equal(Object.keys(data.strings).length);",
                  "});"
                ]
              }
            }
          ],
          "request": {
            "auth": {
              "type": "noauth"
            },
            "method": "GET",
            "header": [
              {
                "key": "X-Grav-Environment",
                "value": "{{grav_environment}}"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url}}{{api_prefix}}/translations/en",
              "host": [
                "{{base_url}}{{api_prefix}}/translations/en"
              ]
            },
            "description": "Get all translation strings for English. No authentication required."
          },
          "response": []
        },
        {
          "name": "Get Translations with Prefix Filter",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('Status is 200', () => pm.response.to.have.status(200));",
                  "pm.test('All keys have prefix', () => {",
                  "    const strings = pm.response.json().data.strings;",
                  "    Object.keys(strings).forEach(key => {",
                  "        pm.expect(key.toUpperCase()).to.match(/^PLUGIN_ADMIN\\./);",
                  "    });",
                  "});"
                ]
              }
            }
          ],
          "request": {
            "auth": {
              "type": "noauth"
            },
            "method": "GET",
            "header": [
              {
                "key": "X-Grav-Environment",
                "value": "{{grav_environment}}"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url}}{{api_prefix}}/translations/en?prefix=PLUGIN_ADMIN",
              "host": [
                "{{base_url}}{{api_prefix}}/translations/en?prefix=PLUGIN_ADMIN"
              ]
            },
            "description": "Get translation strings filtered by PLUGIN_ADMIN prefix. Useful for partial/incremental loading."
          },
          "response": []
        }
      ]
    },
    {
      "name": "MCP",
      "item": [
        {
          "id": "openapi:getMcpTools",
          "name": "Get the MCP tools contributed by plugins",
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "// Generated from openapi.yaml by tests/newman/sync-collection.php; the Newman suite skips it.",
                  "if (pm.variables.get('newman_suite')) { pm.execution.skipRequest(); }"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "X-API-Key",
                "value": "{{api_key}}",
                "description": "API Key authentication"
              },
              {
                "key": "X-Grav-Environment",
                "value": "{{grav_environment}}",
                "description": "Grav environment"
              }
            ],
            "url": {
              "raw": "{{base_url}}{{api_prefix}}/mcp/tools",
              "host": [
                "{{base_url}}{{api_prefix}}"
              ],
              "path": [
                "mcp",
                "tools"
              ]
            },
            "description": "Return every MCP tool the enabled plugins describe, either in an\n`mcp.yaml` manifest at the plugin root or through the `onApiMcpTools`\nevent. An MCP server calls this at startup and registers one tool per\nentry.\n\nTools are filtered by the caller's permissions: a tool naming a\npermission the caller does not hold is left out, tools naming no\npermission are always included, and a super admin sees everything.\n`plugins[].tools` counts what this caller can see.\n\n`fingerprint` hashes the enabled-plugin set plus the manifest file\nmodification times, so a client can tell whether anything changed\nwithout diffing the list. It is also the response's `ETag`, and a\nmatching `If-None-Match` gets a 304.\n\nRequires `api.access`."
          }
        }
      ]
    },
    {
      "name": "Thumbnails",
      "item": [
        {
          "id": "openapi:getThumbnail",
          "name": "Serve cached thumbnail",
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "// Generated from openapi.yaml by tests/newman/sync-collection.php; the Newman suite skips it.",
                  "if (pm.variables.get('newman_suite')) { pm.execution.skipRequest(); }"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "X-Grav-Environment",
                "value": "{{grav_environment}}",
                "description": "Grav environment"
              }
            ],
            "url": {
              "raw": "{{base_url}}{{api_prefix}}/thumbnails/:file",
              "host": [
                "{{base_url}}{{api_prefix}}"
              ],
              "path": [
                "thumbnails",
                ":file"
              ],
              "variable": [
                {
                  "key": "file",
                  "value": "",
                  "description": "Thumbnail filename (an MD5 hash plus the output extension)."
                }
              ]
            },
            "description": "Serve a thumbnail image. This endpoint is public and does not require authentication. The `file` parameter is the hashed filename found in a media item's `thumbnail_url`, a user's `avatar_url` or a theme's `thumbnail`/`screenshot`. Only the last path segment is used, and only names of the form `<32 hex characters>.<jpg|png|gif|webp|avif>` are served.\n\nListings hand out these URLs without resizing anything. The first request for a URL generates the thumbnail, from the source image and at the size the listing recorded for that exact filename, and every later request is served from the cache. Nothing else can be generated here: a filename no listing handed out, or one whose source image has changed or gone since (its hash no longer matches), returns 404, and so does a source that can't be decoded."
          }
        }
      ]
    },
    {
      "name": "Menubar",
      "item": [
        {
          "id": "openapi:listMenubarItems",
          "name": "List plugin toolbar buttons",
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "// Generated from openapi.yaml by tests/newman/sync-collection.php; the Newman suite skips it.",
                  "if (pm.variables.get('newman_suite')) { pm.execution.skipRequest(); }"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "X-API-Key",
                "value": "{{api_key}}",
                "description": "API Key authentication"
              },
              {
                "key": "X-Grav-Environment",
                "value": "{{grav_environment}}",
                "description": "Grav environment"
              }
            ],
            "url": {
              "raw": "{{base_url}}{{api_prefix}}/menubar/items",
              "host": [
                "{{base_url}}{{api_prefix}}"
              ],
              "path": [
                "menubar",
                "items"
              ]
            },
            "description": "Return the toolbar buttons plugins register through the `onApiMenubarItems` event, filtered to those whose `authorize` the current user satisfies. Requires `api.access`."
          }
        },
        {
          "id": "openapi:executeMenubarAction",
          "name": "Run a toolbar action",
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "// Generated from openapi.yaml by tests/newman/sync-collection.php; the Newman suite skips it.",
                  "if (pm.variables.get('newman_suite')) { pm.execution.skipRequest(); }"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "X-API-Key",
                "value": "{{api_key}}",
                "description": "API Key authentication"
              },
              {
                "key": "X-Grav-Environment",
                "value": "{{grav_environment}}",
                "description": "Grav environment"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url}}{{api_prefix}}/menubar/actions/:plugin/:action",
              "host": [
                "{{base_url}}{{api_prefix}}"
              ],
              "path": [
                "menubar",
                "actions",
                ":plugin",
                ":action"
              ],
              "variable": [
                {
                  "key": "plugin",
                  "value": "",
                  "description": "Slug of the plugin that owns the action."
                },
                {
                  "key": "action",
                  "value": "",
                  "description": "Action key from the toolbar item."
                }
              ]
            },
            "description": "Run a plugin's toolbar action through the `onApiMenubarAction` event. When a registered toolbar item matches this plugin and action, the caller must satisfy its `authorize`, or the call returns 403. An action with no matching item only needs `api.access`, and its handler is expected to check permissions itself. A handler that runs and reports a failure still returns 200, with `status: error` in the body; 404 means no plugin handled the action. Requires `api.access`.",
            "body": {
              "mode": "raw",
              "raw": "{}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        }
      ]
    },
    {
      "name": "Groups",
      "item": [
        {
          "id": "openapi:listGroups",
          "name": "List user groups",
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "// Generated from openapi.yaml by tests/newman/sync-collection.php; the Newman suite skips it.",
                  "if (pm.variables.get('newman_suite')) { pm.execution.skipRequest(); }"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "X-API-Key",
                "value": "{{api_key}}",
                "description": "API Key authentication"
              },
              {
                "key": "X-Grav-Environment",
                "value": "{{grav_environment}}",
                "description": "Grav environment"
              }
            ],
            "url": {
              "raw": "{{base_url}}{{api_prefix}}/groups",
              "host": [
                "{{base_url}}{{api_prefix}}"
              ],
              "path": [
                "groups"
              ],
              "query": [
                {
                  "key": "page",
                  "value": "1",
                  "description": "Page number for pagination (1-based).",
                  "disabled": true
                },
                {
                  "key": "per_page",
                  "value": "20",
                  "description": "Number of items per page. Capped at `pagination.max_per_page` in the plugin config (default 1000); values below 1 count as 1.",
                  "disabled": true
                },
                {
                  "key": "search",
                  "value": "",
                  "description": "Filter groups by text. With Flex this uses the directory's search; without it, a case-insensitive match on group name, readable name and description.",
                  "disabled": true
                }
              ]
            },
            "description": "Returns a paginated list of user groups sorted by group name. Requires `api.users.read`. Uses the Flex `user-groups` directory when it is registered and falls back to reading `groups` config otherwise."
          }
        },
        {
          "id": "openapi:createGroup",
          "name": "Create a user group",
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "// Generated from openapi.yaml by tests/newman/sync-collection.php; the Newman suite skips it.",
                  "if (pm.variables.get('newman_suite')) { pm.execution.skipRequest(); }"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "X-API-Key",
                "value": "{{api_key}}",
                "description": "API Key authentication"
              },
              {
                "key": "X-Grav-Environment",
                "value": "{{grav_environment}}",
                "description": "Grav environment"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url}}{{api_prefix}}/groups",
              "host": [
                "{{base_url}}{{api_prefix}}"
              ],
              "path": [
                "groups"
              ]
            },
            "description": "Adds a group to the base `user/config/groups.yaml` (visible in every environment) and clears the standard cache. Requires super-admin (`admin.super`). `enabled` defaults to true and `access` to an empty map. Fires `onApiGroupCreated`.",
            "body": {
              "mode": "raw",
              "raw": "[]",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "id": "openapi:getGroup",
          "name": "Get a user group",
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "// Generated from openapi.yaml by tests/newman/sync-collection.php; the Newman suite skips it.",
                  "if (pm.variables.get('newman_suite')) { pm.execution.skipRequest(); }"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "X-API-Key",
                "value": "{{api_key}}",
                "description": "API Key authentication"
              },
              {
                "key": "X-Grav-Environment",
                "value": "{{grav_environment}}",
                "description": "Grav environment"
              }
            ],
            "url": {
              "raw": "{{base_url}}{{api_prefix}}/groups/:name",
              "host": [
                "{{base_url}}{{api_prefix}}"
              ],
              "path": [
                "groups",
                ":name"
              ],
              "variable": [
                {
                  "key": "name",
                  "value": "",
                  "description": "Group name."
                }
              ]
            },
            "description": "Returns a single group. Requires `api.users.read`. The response includes an `ETag` for use with `If-Match` on PATCH."
          }
        },
        {
          "id": "openapi:updateGroup",
          "name": "Update a user group",
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "// Generated from openapi.yaml by tests/newman/sync-collection.php; the Newman suite skips it.",
                  "if (pm.variables.get('newman_suite')) { pm.execution.skipRequest(); }"
                ]
              }
            }
          ],
          "request": {
            "method": "PATCH",
            "header": [
              {
                "key": "X-API-Key",
                "value": "{{api_key}}",
                "description": "API Key authentication"
              },
              {
                "key": "X-Grav-Environment",
                "value": "{{grav_environment}}",
                "description": "Grav environment"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url}}{{api_prefix}}/groups/:name",
              "host": [
                "{{base_url}}{{api_prefix}}"
              ],
              "path": [
                "groups",
                ":name"
              ],
              "variable": [
                {
                  "key": "name",
                  "value": "",
                  "description": "Group name."
                }
              ]
            },
            "description": "Updates the supplied fields of a group and saves `user/config/groups.yaml`. Requires super-admin (`admin.super`). `access` replaces the whole access map rather than merging into it. Renaming is not supported: `groupname` in the body is ignored. Send the current `ETag` in `If-Match` to detect concurrent edits. Fires `onApiGroupUpdated`.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"readableName\": \"Editors\",\n  \"description\": \"Can edit and publish pages.\",\n  \"icon\": \"users\",\n  \"enabled\": true,\n  \"access\": {}\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "id": "openapi:deleteGroup",
          "name": "Delete a user group",
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "// Generated from openapi.yaml by tests/newman/sync-collection.php; the Newman suite skips it.",
                  "if (pm.variables.get('newman_suite')) { pm.execution.skipRequest(); }"
                ]
              }
            }
          ],
          "request": {
            "method": "DELETE",
            "header": [
              {
                "key": "X-API-Key",
                "value": "{{api_key}}",
                "description": "API Key authentication"
              },
              {
                "key": "X-Grav-Environment",
                "value": "{{grav_environment}}",
                "description": "Grav environment"
              }
            ],
            "url": {
              "raw": "{{base_url}}{{api_prefix}}/groups/:name",
              "host": [
                "{{base_url}}{{api_prefix}}"
              ],
              "path": [
                "groups",
                ":name"
              ],
              "variable": [
                {
                  "key": "name",
                  "value": "",
                  "description": "Group name."
                }
              ]
            },
            "description": "Removes a group from `user/config/groups.yaml`. Requires super-admin (`admin.super`). Accounts that still list the group are not modified. Fires `onApiGroupDeleted`."
          }
        }
      ]
    },
    {
      "name": "Demo",
      "item": [
        {
          "id": "openapi:getDemoStatus",
          "name": "Get demo reset status",
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "// Generated from openapi.yaml by tests/newman/sync-collection.php; the Newman suite skips it.",
                  "if (pm.variables.get('newman_suite')) { pm.execution.skipRequest(); }"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "X-API-Key",
                "value": "{{api_key}}",
                "description": "API Key authentication"
              },
              {
                "key": "X-Grav-Environment",
                "value": "{{grav_environment}}",
                "description": "Grav environment"
              }
            ],
            "url": {
              "raw": "{{base_url}}{{api_prefix}}/demo/status",
              "host": [
                "{{base_url}}{{api_prefix}}"
              ],
              "path": [
                "demo",
                "status"
              ]
            },
            "description": "Returns the demo-mode reset state that drives the admin banner and countdown. Any authenticated user may call it; no specific permission is needed."
          }
        },
        {
          "id": "openapi:captureDemoBaseline",
          "name": "Capture the demo baseline",
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "// Generated from openapi.yaml by tests/newman/sync-collection.php; the Newman suite skips it.",
                  "if (pm.variables.get('newman_suite')) { pm.execution.skipRequest(); }"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "X-API-Key",
                "value": "{{api_key}}",
                "description": "API Key authentication"
              },
              {
                "key": "X-Grav-Environment",
                "value": "{{grav_environment}}",
                "description": "Grav environment"
              }
            ],
            "url": {
              "raw": "{{base_url}}{{api_prefix}}/demo/baseline",
              "host": [
                "{{base_url}}{{api_prefix}}"
              ],
              "path": [
                "demo",
                "baseline"
              ]
            },
            "description": "Snapshots the configured writable demo resources as the baseline that resets restore. Requires super-admin (`admin.super`). Demo accounts are always refused, even when they are super. When no writable resources are configured nothing is captured and `captured` is false (still 200). Fires `onApiDemoBaselineCaptured` on success."
          }
        },
        {
          "id": "openapi:resetDemo",
          "name": "Reset demo content to the baseline",
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "// Generated from openapi.yaml by tests/newman/sync-collection.php; the Newman suite skips it.",
                  "if (pm.variables.get('newman_suite')) { pm.execution.skipRequest(); }"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "X-API-Key",
                "value": "{{api_key}}",
                "description": "API Key authentication"
              },
              {
                "key": "X-Grav-Environment",
                "value": "{{grav_environment}}",
                "description": "Grav environment"
              }
            ],
            "url": {
              "raw": "{{base_url}}{{api_prefix}}/demo/reset",
              "host": [
                "{{base_url}}{{api_prefix}}"
              ],
              "path": [
                "demo",
                "reset"
              ]
            },
            "description": "Restores the demo content from the captured baseline, waiting for any background reset already in progress. Requires super-admin (`admin.super`). Demo accounts are always refused, even when they are super. Without a baseline nothing happens and `reset` is false (still 200). Fires `onApiDemoReset`."
          }
        }
      ]
    },
    {
      "name": "Audit",
      "item": [
        {
          "id": "openapi:getAuditStatus",
          "name": "Get audit trail status",
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "// Generated from openapi.yaml by tests/newman/sync-collection.php; the Newman suite skips it.",
                  "if (pm.variables.get('newman_suite')) { pm.execution.skipRequest(); }"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "X-API-Key",
                "value": "{{api_key}}",
                "description": "API Key authentication"
              },
              {
                "key": "X-Grav-Environment",
                "value": "{{grav_environment}}",
                "description": "Grav environment"
              }
            ],
            "url": {
              "raw": "{{base_url}}{{api_prefix}}/audit/status",
              "host": [
                "{{base_url}}{{api_prefix}}"
              ],
              "path": [
                "audit",
                "status"
              ]
            },
            "description": "Whether the audit trail is available and enabled, with its detail level and retention settings. The admin uses it to decide whether to show the Audit tab. Works even when the trail is disabled.\n\nRequires super admin (`api.super`); a scoped API key must carry the `admin.super` scope, the same as every other super-only endpoint. Demo accounts are rejected, since the log holds other visitors' IPs and user agents."
          }
        },
        {
          "id": "openapi:listAuditEvents",
          "name": "List audit events",
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "// Generated from openapi.yaml by tests/newman/sync-collection.php; the Newman suite skips it.",
                  "if (pm.variables.get('newman_suite')) { pm.execution.skipRequest(); }"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "X-API-Key",
                "value": "{{api_key}}",
                "description": "API Key authentication"
              },
              {
                "key": "X-Grav-Environment",
                "value": "{{grav_environment}}",
                "description": "Grav environment"
              }
            ],
            "url": {
              "raw": "{{base_url}}{{api_prefix}}/audit/events",
              "host": [
                "{{base_url}}{{api_prefix}}"
              ],
              "path": [
                "audit",
                "events"
              ],
              "query": [
                {
                  "key": "page",
                  "value": "1",
                  "description": "Page number for pagination (1-based).",
                  "disabled": true
                },
                {
                  "key": "per_page",
                  "value": "50",
                  "description": "Events per page. Defaults to 50, capped at `plugins.api.pagination.max_per_page` (default 1000).",
                  "disabled": true
                },
                {
                  "key": "event",
                  "value": "",
                  "description": "Exact event code.",
                  "disabled": true
                },
                {
                  "key": "actor",
                  "value": "",
                  "description": "Exact actor id or actor name.",
                  "disabled": true
                },
                {
                  "key": "target_type",
                  "value": "",
                  "description": "Exact target type (for example `page`, `media`, `user`, `group`, `config`, `package`).",
                  "disabled": true
                },
                {
                  "key": "severity",
                  "value": "",
                  "description": "Exact severity. Recorded events use `info`, `notice` or `warning`.",
                  "disabled": true
                },
                {
                  "key": "from",
                  "value": "0",
                  "description": "Earliest timestamp, inclusive, in Unix epoch milliseconds.",
                  "disabled": true
                },
                {
                  "key": "to",
                  "value": "0",
                  "description": "Latest timestamp, inclusive, in Unix epoch milliseconds.",
                  "disabled": true
                },
                {
                  "key": "q",
                  "value": "",
                  "description": "Case-insensitive substring match against actor name, target id, event code and IP.",
                  "disabled": true
                }
              ]
            },
            "description": "Paginated audit events, newest first, with optional filters. The trail records API-layer activity, which covers everything done through Admin Next plus API-key clients; classic admin actions that bypass the API are not recorded.\n\n`per_page` defaults to 50. The pagination `links` carry the active filters along with `page` and `per_page`.\n\nRequires super admin (`api.super`); a scoped API key needs the `admin.super` scope. Demo accounts are rejected. Returns 404 when the trail is disabled and 503 when SQLite is missing."
          }
        },
        {
          "id": "openapi:getAuditFacets",
          "name": "Get audit filter options",
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "// Generated from openapi.yaml by tests/newman/sync-collection.php; the Newman suite skips it.",
                  "if (pm.variables.get('newman_suite')) { pm.execution.skipRequest(); }"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "X-API-Key",
                "value": "{{api_key}}",
                "description": "API Key authentication"
              },
              {
                "key": "X-Grav-Environment",
                "value": "{{grav_environment}}",
                "description": "Grav environment"
              }
            ],
            "url": {
              "raw": "{{base_url}}{{api_prefix}}/audit/facets",
              "host": [
                "{{base_url}}{{api_prefix}}"
              ],
              "path": [
                "audit",
                "facets"
              ]
            },
            "description": "Distinct event codes and actors found in the log, for the filter drop-downs.\n\nRequires super admin (`api.super`); a scoped API key needs the `admin.super` scope. Demo accounts are rejected. Returns 404 when the trail is disabled and 503 when SQLite is missing."
          }
        },
        {
          "id": "openapi:exportAuditLog",
          "name": "Download the audit log",
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "// Generated from openapi.yaml by tests/newman/sync-collection.php; the Newman suite skips it.",
                  "if (pm.variables.get('newman_suite')) { pm.execution.skipRequest(); }"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "X-API-Key",
                "value": "{{api_key}}",
                "description": "API Key authentication"
              },
              {
                "key": "X-Grav-Environment",
                "value": "{{grav_environment}}",
                "description": "Grav environment"
              }
            ],
            "url": {
              "raw": "{{base_url}}{{api_prefix}}/audit/export",
              "host": [
                "{{base_url}}{{api_prefix}}"
              ],
              "path": [
                "audit",
                "export"
              ],
              "query": [
                {
                  "key": "format",
                  "value": "csv",
                  "description": "",
                  "disabled": true
                },
                {
                  "key": "event",
                  "value": "",
                  "description": "Exact event code.",
                  "disabled": true
                },
                {
                  "key": "actor",
                  "value": "",
                  "description": "Exact actor id or actor name.",
                  "disabled": true
                },
                {
                  "key": "target_type",
                  "value": "",
                  "description": "Exact target type (for example `page`, `media`, `user`, `group`, `config`, `package`).",
                  "disabled": true
                },
                {
                  "key": "severity",
                  "value": "",
                  "description": "Exact severity. Recorded events use `info`, `notice` or `warning`.",
                  "disabled": true
                },
                {
                  "key": "from",
                  "value": "0",
                  "description": "Earliest timestamp, inclusive, in Unix epoch milliseconds.",
                  "disabled": true
                },
                {
                  "key": "to",
                  "value": "0",
                  "description": "Latest timestamp, inclusive, in Unix epoch milliseconds.",
                  "disabled": true
                },
                {
                  "key": "q",
                  "value": "",
                  "description": "Case-insensitive substring match against actor name, target id, event code and IP.",
                  "disabled": true
                }
              ]
            },
            "description": "Downloads every event matching the filters (newest first, capped at 100,000 rows) as an attachment, not wrapped in the JSON envelope. CSV is the default; `format=json` returns a bare JSON array of events. Any other `format` value falls back to CSV.\n\nCSV columns: `id, ts, event, severity, actor_id, actor_name, actor_roles, auth_method, ip, user_agent, target_type, target_id, status, context`. Array values are JSON-encoded, and cells starting with `=`, `+`, `-`, `@`, tab or CR are prefixed with `'` to stop spreadsheet formula execution.\n\nSent with `Cache-Control: no-store`. Requires super admin (`api.super`); a scoped API key needs the `admin.super` scope. Demo accounts are rejected. Returns 404 when the trail is disabled and 503 when SQLite is missing."
          }
        }
      ]
    },
    {
      "name": "Translations Editor",
      "item": [
        {
          "id": "openapi:listI18nSources",
          "name": "List translation sources",
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "// Generated from openapi.yaml by tests/newman/sync-collection.php; the Newman suite skips it.",
                  "if (pm.variables.get('newman_suite')) { pm.execution.skipRequest(); }"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "X-API-Key",
                "value": "{{api_key}}",
                "description": "API Key authentication"
              },
              {
                "key": "X-Grav-Environment",
                "value": "{{grav_environment}}",
                "description": "Grav environment"
              }
            ],
            "url": {
              "raw": "{{base_url}}{{api_prefix}}/i18n/sources",
              "host": [
                "{{base_url}}{{api_prefix}}"
              ],
              "path": [
                "i18n",
                "sources"
              ],
              "query": [
                {
                  "key": "lang",
                  "value": "",
                  "description": "Language to count keys in. Defaults to the editor's default language (the site default if any source ships it, else `en`, else the first available).",
                  "disabled": true
                }
              ]
            },
            "description": "Every source (Grav core, plugins, themes, and this site's own overrides) that ships at least one string in the requested language, with the key namespaces it contributes. Drives the editor's browse pane.\n\nRequires `api.translations.read`."
          }
        },
        {
          "id": "openapi:listI18nLanguages",
          "name": "List translation languages",
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "// Generated from openapi.yaml by tests/newman/sync-collection.php; the Newman suite skips it.",
                  "if (pm.variables.get('newman_suite')) { pm.execution.skipRequest(); }"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "X-API-Key",
                "value": "{{api_key}}",
                "description": "API Key authentication"
              },
              {
                "key": "X-Grav-Environment",
                "value": "{{grav_environment}}",
                "description": "Grav environment"
              }
            ],
            "url": {
              "raw": "{{base_url}}{{api_prefix}}/i18n/languages",
              "host": [
                "{{base_url}}{{api_prefix}}"
              ],
              "path": [
                "i18n",
                "languages"
              ]
            },
            "description": "Every language code that any source ships, flagged with whether this site has an override file for it, plus the editor's default language.\n\nRequires `api.translations.read`."
          }
        },
        {
          "id": "openapi:getI18nCoverage",
          "name": "Get translation coverage per language",
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "// Generated from openapi.yaml by tests/newman/sync-collection.php; the Newman suite skips it.",
                  "if (pm.variables.get('newman_suite')) { pm.execution.skipRequest(); }"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "X-API-Key",
                "value": "{{api_key}}",
                "description": "API Key authentication"
              },
              {
                "key": "X-Grav-Environment",
                "value": "{{grav_environment}}",
                "description": "Grav environment"
              }
            ],
            "url": {
              "raw": "{{base_url}}{{api_prefix}}/i18n/coverage",
              "host": [
                "{{base_url}}{{api_prefix}}"
              ],
              "path": [
                "i18n",
                "coverage"
              ],
              "query": [
                {
                  "key": "source_lang",
                  "value": "",
                  "description": "Language used as the reference. An invalid or missing code falls back to the editor's default language (the site default if any source ships it, else `en`, else the first available).",
                  "disabled": true
                }
              ]
            },
            "description": "For every language, how many of the source language's keys it has (shipped or overridden) and how many are missing. Measured against the source language's key set only, so stale keys that exist only in an old translation do not count.\n\nRequires `api.translations.read`."
          }
        },
        {
          "id": "openapi:listI18nKeys",
          "name": "List translation keys",
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "// Generated from openapi.yaml by tests/newman/sync-collection.php; the Newman suite skips it.",
                  "if (pm.variables.get('newman_suite')) { pm.execution.skipRequest(); }"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "X-API-Key",
                "value": "{{api_key}}",
                "description": "API Key authentication"
              },
              {
                "key": "X-Grav-Environment",
                "value": "{{grav_environment}}",
                "description": "Grav environment"
              }
            ],
            "url": {
              "raw": "{{base_url}}{{api_prefix}}/i18n/keys",
              "host": [
                "{{base_url}}{{api_prefix}}"
              ],
              "path": [
                "i18n",
                "keys"
              ],
              "query": [
                {
                  "key": "source_lang",
                  "value": "",
                  "description": "Language used as the reference. An invalid or missing code falls back to the editor's default language (the site default if any source ships it, else `en`, else the first available).",
                  "disabled": true
                },
                {
                  "key": "langs",
                  "value": "",
                  "description": "Comma-separated language codes to include as cells. Codes no source ships are ignored; defaults to the source language alone.",
                  "disabled": true
                },
                {
                  "key": "q",
                  "value": "",
                  "description": "Search text, matched against the key and the source value.",
                  "disabled": true
                },
                {
                  "key": "provider",
                  "value": "",
                  "description": "Limit to keys shipped by one source id (see `GET /i18n/sources`).",
                  "disabled": true
                },
                {
                  "key": "namespace",
                  "value": "",
                  "description": "Limit to keys equal to or under this dotted prefix.",
                  "disabled": true
                },
                {
                  "key": "status",
                  "value": "all",
                  "description": "",
                  "disabled": true
                },
                {
                  "key": "page",
                  "value": "1",
                  "description": "Page number for pagination (1-based).",
                  "disabled": true
                },
                {
                  "key": "per_page",
                  "value": "20",
                  "description": "Rows per page. Capped at 500.",
                  "disabled": true
                }
              ]
            },
            "description": "The editor matrix: one row per key, sorted by key, with the source-language value and a cell per requested language. Keys that only exist as site overrides (typos, strings from removed plugins) are included so they can be fixed.\n\n`q` matches both the key and the source-language value, case-insensitively. `status` is applied to the composed row: a row matches when any requested language's cell has that state; `unknown` matches keys no source ships.\n\n`per_page` is capped at 500. Requires `api.translations.read`."
          }
        },
        {
          "id": "openapi:getI18nKey",
          "name": "Get one translation key",
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "// Generated from openapi.yaml by tests/newman/sync-collection.php; the Newman suite skips it.",
                  "if (pm.variables.get('newman_suite')) { pm.execution.skipRequest(); }"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "X-API-Key",
                "value": "{{api_key}}",
                "description": "API Key authentication"
              },
              {
                "key": "X-Grav-Environment",
                "value": "{{grav_environment}}",
                "description": "Grav environment"
              }
            ],
            "url": {
              "raw": "{{base_url}}{{api_prefix}}/i18n/keys/:key",
              "host": [
                "{{base_url}}{{api_prefix}}"
              ],
              "path": [
                "i18n",
                "keys",
                ":key"
              ],
              "query": [
                {
                  "key": "source_lang",
                  "value": "",
                  "description": "Language used as the reference. An invalid or missing code falls back to the editor's default language (the site default if any source ships it, else `en`, else the first available).",
                  "disabled": true
                }
              ],
              "variable": [
                {
                  "key": "key",
                  "value": "",
                  "description": "Dotted translation key. Only letters, digits, `_`, `.` and `-` are routed; anything else is a 404."
                }
              ]
            },
            "description": "One key across every language any source ships. An unknown key still returns 200, with `known: false` and `missing` cells.\n\nRequires `api.translations.read`."
          }
        },
        {
          "id": "openapi:getI18nOverrides",
          "name": "Get a language's override file as YAML",
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "// Generated from openapi.yaml by tests/newman/sync-collection.php; the Newman suite skips it.",
                  "if (pm.variables.get('newman_suite')) { pm.execution.skipRequest(); }"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "X-API-Key",
                "value": "{{api_key}}",
                "description": "API Key authentication"
              },
              {
                "key": "X-Grav-Environment",
                "value": "{{grav_environment}}",
                "description": "Grav environment"
              }
            ],
            "url": {
              "raw": "{{base_url}}{{api_prefix}}/i18n/overrides/:lang",
              "host": [
                "{{base_url}}{{api_prefix}}"
              ],
              "path": [
                "i18n",
                "overrides",
                ":lang"
              ],
              "query": [
                {
                  "key": "namespace",
                  "value": "",
                  "description": "Dotted key prefix to limit the output to.",
                  "disabled": true
                }
              ],
              "variable": [
                {
                  "key": "lang",
                  "value": "{{lang}}",
                  "description": "Language code of the override file (`user/languages/<lang>.yaml`)."
                }
              ]
            },
            "description": "The site's override file for one language (`user/languages/<lang>.yaml`), for the advanced YAML editor. Without `namespace` this is the raw file text (empty string when no file exists). With `namespace`, only overrides under that prefix are returned, re-dumped as nested YAML, so \"edit as YAML\" covers only what is on screen.\n\nRequires `api.translations.read`. An invalid language code returns 422, as it does for PATCH and PUT."
          }
        },
        {
          "id": "openapi:replaceI18nOverrides",
          "name": "Replace a language's overrides from YAML",
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "// Generated from openapi.yaml by tests/newman/sync-collection.php; the Newman suite skips it.",
                  "if (pm.variables.get('newman_suite')) { pm.execution.skipRequest(); }"
                ]
              }
            }
          ],
          "request": {
            "method": "PUT",
            "header": [
              {
                "key": "X-API-Key",
                "value": "{{api_key}}",
                "description": "API Key authentication"
              },
              {
                "key": "X-Grav-Environment",
                "value": "{{grav_environment}}",
                "description": "Grav environment"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url}}{{api_prefix}}/i18n/overrides/:lang",
              "host": [
                "{{base_url}}{{api_prefix}}"
              ],
              "path": [
                "i18n",
                "overrides",
                ":lang"
              ],
              "variable": [
                {
                  "key": "lang",
                  "value": "{{lang}}",
                  "description": "Language code of the override file (`user/languages/<lang>.yaml`)."
                }
              ]
            },
            "description": "Advanced-mode save. Parses `yaml` (a mapping, nested or dotted) and replaces the language's overrides with it. With `namespace`, only overrides under that prefix are replaced and everything else in the file is kept; a key outside the namespace fails the whole save with 422, and the error names the out-of-scope keys (up to five). Empty `yaml` deletes every override in scope.\n\nValues equal to the shipped value are dropped (`dropped`); keys no source ships are saved but reported (`unknown`) rather than rejected. Sends `X-Invalidates: i18n:update, translations:update`.\n\nRequires `api.translations.write`; demo accounts are rejected.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"yaml\": \"THEME_QUARK:\\n  SEARCH: Rechercher\\n\",\n  \"namespace\": \"THEME_QUARK\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "id": "openapi:patchI18nOverrides",
          "name": "Set or remove individual overrides",
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "// Generated from openapi.yaml by tests/newman/sync-collection.php; the Newman suite skips it.",
                  "if (pm.variables.get('newman_suite')) { pm.execution.skipRequest(); }"
                ]
              }
            }
          ],
          "request": {
            "method": "PATCH",
            "header": [
              {
                "key": "X-API-Key",
                "value": "{{api_key}}",
                "description": "API Key authentication"
              },
              {
                "key": "X-Grav-Environment",
                "value": "{{grav_environment}}",
                "description": "Grav environment"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url}}{{api_prefix}}/i18n/overrides/:lang",
              "host": [
                "{{base_url}}{{api_prefix}}"
              ],
              "path": [
                "i18n",
                "overrides",
                ":lang"
              ],
              "query": [
                {
                  "key": "source_lang",
                  "value": "",
                  "description": "Language used as the reference. An invalid or missing code falls back to the editor's default language (the site default if any source ships it, else `en`, else the first available).",
                  "disabled": true
                }
              ],
              "variable": [
                {
                  "key": "lang",
                  "value": "{{lang}}",
                  "description": "Language code of the override file (`user/languages/<lang>.yaml`)."
                }
              ]
            },
            "description": "Inline edits to one language's override file. `set` writes values; `unset` removes overrides so the shipped value shows again. A `set` whose value equals what the source ships is stored as a removal instead (reported in `reverted`), so the file only holds real differences. Keys no source ships are still written and reported in `unknown`.\n\nThe response includes a fresh matrix row for every touched key, with a cell for this language only. Sends `X-Invalidates: i18n:update, translations:update`.\n\nRequires `api.translations.write`; demo accounts are rejected.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"set\": {\n    \"THEME_QUARK.SEARCH\": \"Rechercher\"\n  },\n  \"unset\": [\n    \"PLUGIN_ADMIN.TITLE\"\n  ]\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "id": "openapi:getI18nTranslateStatus",
          "name": "Check whether machine translation is available",
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "// Generated from openapi.yaml by tests/newman/sync-collection.php; the Newman suite skips it.",
                  "if (pm.variables.get('newman_suite')) { pm.execution.skipRequest(); }"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "X-API-Key",
                "value": "{{api_key}}",
                "description": "API Key authentication"
              },
              {
                "key": "X-Grav-Environment",
                "value": "{{grav_environment}}",
                "description": "Grav environment"
              }
            ],
            "url": {
              "raw": "{{base_url}}{{api_prefix}}/i18n/translate",
              "host": [
                "{{base_url}}{{api_prefix}}"
              ],
              "path": [
                "i18n",
                "translate"
              ]
            },
            "description": "Whether the AI Translate plugin can propose translations, and if not, why: it is not installed, not enabled, or has no configured provider. The editor hides its translate actions when unavailable.\n\nRequires `api.translations.read`."
          }
        },
        {
          "id": "openapi:translateI18nKeys",
          "name": "Propose machine translations",
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "// Generated from openapi.yaml by tests/newman/sync-collection.php; the Newman suite skips it.",
                  "if (pm.variables.get('newman_suite')) { pm.execution.skipRequest(); }"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "X-API-Key",
                "value": "{{api_key}}",
                "description": "API Key authentication"
              },
              {
                "key": "X-Grav-Environment",
                "value": "{{grav_environment}}",
                "description": "Grav environment"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url}}{{api_prefix}}/i18n/translate",
              "host": [
                "{{base_url}}{{api_prefix}}"
              ],
              "path": [
                "i18n",
                "translate"
              ]
            },
            "description": "Sends up to 200 keys' source values to the AI Translate plugin and returns proposals. Nothing is written; the caller reviews the proposals and saves them with `PATCH /i18n/overrides/{lang}`.\n\nPlaceholders are masked before sending and checked afterwards; a proposal whose placeholders came back altered is refused (`placeholders_mangled`). ICU strings that need a human, keys with no source value, and strings with nothing translatable are skipped without calling the provider. Proposals come back in the order the keys were sent.\n\nRequires `api.translations.write` (it spends provider credits); demo accounts are rejected. Returns 422 when machine translation is unavailable or the provider call fails.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"source_lang\": \"en\",\n  \"target_lang\": \"fr\",\n  \"keys\": [\n    \"THEME_QUARK.SEARCH\",\n    \"PLUGIN_ADMIN.TITLE\"\n  ]\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "id": "openapi:getI18nImportStatus",
          "name": "Preview an import from the translation-strings plugin",
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "// Generated from openapi.yaml by tests/newman/sync-collection.php; the Newman suite skips it.",
                  "if (pm.variables.get('newman_suite')) { pm.execution.skipRequest(); }"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "X-API-Key",
                "value": "{{api_key}}",
                "description": "API Key authentication"
              },
              {
                "key": "X-Grav-Environment",
                "value": "{{grav_environment}}",
                "description": "Grav environment"
              }
            ],
            "url": {
              "raw": "{{base_url}}{{api_prefix}}/i18n/import/translation-strings",
              "host": [
                "{{base_url}}{{api_prefix}}"
              ],
              "path": [
                "i18n",
                "import",
                "translation-strings"
              ]
            },
            "description": "What the old translation-strings plugin has configured and what importing it would do, per language. Counts are exact; `keys` lists at most 50 keys per language. Read-only.\n\nRequires `api.translations.read`. Returns 422 when the plugin's config holds a language code the editor cannot store."
          }
        },
        {
          "id": "openapi:importI18nTranslationStrings",
          "name": "Import overrides from the translation-strings plugin",
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "// Generated from openapi.yaml by tests/newman/sync-collection.php; the Newman suite skips it.",
                  "if (pm.variables.get('newman_suite')) { pm.execution.skipRequest(); }"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "X-API-Key",
                "value": "{{api_key}}",
                "description": "API Key authentication"
              },
              {
                "key": "X-Grav-Environment",
                "value": "{{grav_environment}}",
                "description": "Grav environment"
              }
            ],
            "url": {
              "raw": "{{base_url}}{{api_prefix}}/i18n/import/translation-strings",
              "host": [
                "{{base_url}}{{api_prefix}}"
              ],
              "path": [
                "i18n",
                "import",
                "translation-strings"
              ]
            },
            "description": "Merges every override configured in the translation-strings plugin into `user/languages/<lang>.yaml`. Existing overrides for other keys are kept; where both name the same key, the plugin's value wins because that is what the site shows today.\n\nThe plugin is not disabled; the client does that afterwards through the config endpoint. `plugin_enabled` reports its current state. Sends `X-Invalidates: translations:update`.\n\nRequires `api.translations.write`; demo accounts are rejected. Returns 422 when the plugin has nothing configured, or when its config holds a language code the editor cannot store (checked before anything is written)."
          }
        }
      ]
    },
    {
      "name": "Admin Next",
      "item": [
        {
          "id": "openapi:getAdminNextBoot",
          "name": "Everything Admin Next loads at startup, in one request",
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "// Generated from openapi.yaml by tests/newman/sync-collection.php; the Newman suite skips it.",
                  "if (pm.variables.get('newman_suite')) { pm.execution.skipRequest(); }"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "X-API-Key",
                "value": "{{api_key}}",
                "description": "API Key authentication"
              },
              {
                "key": "X-Grav-Environment",
                "value": "{{grav_environment}}",
                "description": "Grav environment"
              }
            ],
            "url": {
              "raw": "{{base_url}}{{api_prefix}}/admin-next/boot",
              "host": [
                "{{base_url}}{{api_prefix}}"
              ],
              "path": [
                "admin-next",
                "boot"
              ],
              "query": [
                {
                  "key": "lang",
                  "value": "",
                  "description": "Admin UI language to report the translations checksum for.",
                  "disabled": true
                }
              ]
            },
            "description": "Returns, in one response, what Admin Next otherwise fetches with nine requests when it starts. Each member of `data` is exactly the `data` of its own endpoint, produced by the same code with the same permission check:\n\n| Part | Same as |\n| --- | --- |\n| `preferences` | `GET /admin-next/preferences` |\n| `me` | `GET /me` |\n| `menubar` | `GET /menubar/items` |\n| `sidebar` | `GET /sidebar/items` |\n| `floating_widgets` | `GET /floating-widgets` |\n| `context_panels` | `GET /context-panels` |\n| `custom_fields` | `GET /custom-fields` |\n| `languages` | `GET /languages` |\n| `translations` | the `lang` and `checksum` of `GET /translations/{lang}` (the checksum is the ETag that endpoint sends), without the strings |\n\nA part the caller may not read, or one that fails, is left out of `data` and reported under the same key in `errors` with the status and title its own endpoint would have answered with (for example `languages` needs `api.pages.read`). One failing part never fails the response. `errors` is always present, as an empty object when every part succeeded.\n\nThe translations checksum is for `?lang=` when given, otherwise the caller's admin language (`preferences.effective.adminLanguage`), resolved exactly as `/translations/{lang}` resolves its path parameter: a malformed code falls back to the site default, and short codes are expanded (`en` becomes `en-US`). A client that already holds that dictionary with the same checksum can skip `/translations` entirely; otherwise it fetches `/translations/{lang}` as before.\n\nRequires `api.access`."
          }
        },
        {
          "id": "openapi:getAdminNextPreferences",
          "name": "Get resolved Admin Next preferences",
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "// Generated from openapi.yaml by tests/newman/sync-collection.php; the Newman suite skips it.",
                  "if (pm.variables.get('newman_suite')) { pm.execution.skipRequest(); }"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "X-API-Key",
                "value": "{{api_key}}",
                "description": "API Key authentication"
              },
              {
                "key": "X-Grav-Environment",
                "value": "{{grav_environment}}",
                "description": "Grav environment"
              }
            ],
            "url": {
              "raw": "{{base_url}}{{api_prefix}}/admin-next/preferences",
              "host": [
                "{{base_url}}{{api_prefix}}"
              ],
              "path": [
                "admin-next",
                "preferences"
              ]
            },
            "description": "Returns the full preferences payload Admin Next reads once on boot: site branding (with ready-to-use logo URLs), site defaults, site-only behavioral settings, media upload constraints, the current user's saved overrides, and the `effective` map that merges them all.\n\nResolution order for user-overridable keys is built-in defaults, then site defaults (`ui.defaults`), then the user's overrides (`admin_next.preferences` in the account YAML). If the user has not picked an admin language, a classic-admin `language:` value on the account is honored. Site-only settings (`ui.settings`) are applied last and cannot be overridden per user.\n\nRequires `api.access`. The response carries an `ETag` header computed from the body (conditional GETs are not answered with 304)."
          }
        },
        {
          "id": "openapi:updateAdminNextUserPreferences",
          "name": "Update the current user's preference overrides",
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "// Generated from openapi.yaml by tests/newman/sync-collection.php; the Newman suite skips it.",
                  "if (pm.variables.get('newman_suite')) { pm.execution.skipRequest(); }"
                ]
              }
            }
          ],
          "request": {
            "method": "PATCH",
            "header": [
              {
                "key": "X-API-Key",
                "value": "{{api_key}}",
                "description": "API Key authentication"
              },
              {
                "key": "X-Grav-Environment",
                "value": "{{grav_environment}}",
                "description": "Grav environment"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url}}{{api_prefix}}/admin-next/preferences/user",
              "host": [
                "{{base_url}}{{api_prefix}}"
              ],
              "path": [
                "admin-next",
                "preferences",
                "user"
              ]
            },
            "description": "Patches the calling user's overrides, stored under `admin_next.preferences` in their account YAML. Only the user-overridable keys (see `PreferencesValues`) are accepted; site-only keys such as `autoSaveEnabled` or `menubarLinks` are ignored here.\n\nKeys not present in the body are left alone. A key set to `null` removes that override so the site default applies again. Values that fail validation (unknown enum value, wrong type) are silently dropped rather than rejected; numeric values are clamped to their allowed range.\n\nRequires `api.access`. Demo accounts are refused. Returns the full resolved payload, the same as `GET /admin-next/preferences`.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"colorMode\": \"dark\",\n  \"accentHue\": 200,\n  \"darkShade\": \"midnight\",\n  \"helpMode\": \"tooltip\",\n  \"fontSize\": null\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "id": "openapi:resetAdminNextUserPreferences",
          "name": "Reset the current user's preferences to site defaults",
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "// Generated from openapi.yaml by tests/newman/sync-collection.php; the Newman suite skips it.",
                  "if (pm.variables.get('newman_suite')) { pm.execution.skipRequest(); }"
                ]
              }
            }
          ],
          "request": {
            "method": "DELETE",
            "header": [
              {
                "key": "X-API-Key",
                "value": "{{api_key}}",
                "description": "API Key authentication"
              },
              {
                "key": "X-Grav-Environment",
                "value": "{{grav_environment}}",
                "description": "Grav environment"
              }
            ],
            "url": {
              "raw": "{{base_url}}{{api_prefix}}/admin-next/preferences/user",
              "host": [
                "{{base_url}}{{api_prefix}}"
              ],
              "path": [
                "admin-next",
                "preferences",
                "user"
              ]
            },
            "description": "Removes every override the calling user has saved (the whole `admin_next.preferences` block), backing the \"Reset to site defaults\" action in Admin Next. Dashboard layout (`admin_next.dashboard`) is not touched.\n\nRequires `api.access`. Demo accounts are refused. Returns the full resolved payload."
          }
        },
        {
          "id": "openapi:updateAdminNextSitePreferences",
          "name": "Update site-wide preference defaults and settings",
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "// Generated from openapi.yaml by tests/newman/sync-collection.php; the Newman suite skips it.",
                  "if (pm.variables.get('newman_suite')) { pm.execution.skipRequest(); }"
                ]
              }
            }
          ],
          "request": {
            "method": "PATCH",
            "header": [
              {
                "key": "X-API-Key",
                "value": "{{api_key}}",
                "description": "API Key authentication"
              },
              {
                "key": "X-Grav-Environment",
                "value": "{{grav_environment}}",
                "description": "Grav environment"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url}}{{api_prefix}}/admin-next/preferences/site",
              "host": [
                "{{base_url}}{{api_prefix}}"
              ],
              "path": [
                "admin-next",
                "preferences",
                "site"
              ]
            },
            "description": "Writes site-level preferences to `user/config/admin-next.yaml`. The flat body is split in two:\n\nUser-overridable keys (`PreferencesValues`) are merged into `ui.defaults`, so only the keys sent change. Send `null` for a key to remove the saved default so it falls back to the built-in value. Invalid values are dropped.\n\nSite-only keys (`PreferencesSiteSettings`: auto-save, collaboration, menubar links) are merged into `ui.settings`, so only the keys sent change.\n\nAny other key is ignored. Requires super-admin (`admin.super`, subject to the API-key scope cap). Demo accounts are refused. Returns the full resolved payload.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"colorMode\": \"\",\n  \"accentHue\": 271,\n  \"fontFamily\": \"inter\",\n  \"autoSaveEnabled\": true,\n  \"menubarLinks\": [\n    {\n      \"label\": \"Docs\",\n      \"url\": \"https://learn.getgrav.org\",\n      \"icon\": \"book\",\n      \"external\": true\n    }\n  ]\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "id": "openapi:updateAdminNextBranding",
          "name": "Update site branding",
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "// Generated from openapi.yaml by tests/newman/sync-collection.php; the Newman suite skips it.",
                  "if (pm.variables.get('newman_suite')) { pm.execution.skipRequest(); }"
                ]
              }
            }
          ],
          "request": {
            "method": "PATCH",
            "header": [
              {
                "key": "X-API-Key",
                "value": "{{api_key}}",
                "description": "API Key authentication"
              },
              {
                "key": "X-Grav-Environment",
                "value": "{{grav_environment}}",
                "description": "Grav environment"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url}}{{api_prefix}}/admin-next/branding",
              "host": [
                "{{base_url}}{{api_prefix}}"
              ],
              "path": [
                "admin-next",
                "branding"
              ]
            },
            "description": "Updates the site branding block (`ui.branding` in `user/config/admin-next.yaml`): logo mode, logo text, logo height, sign-in title/subtitle, and the \"Powered by Grav\" toggle. The body is merged over the current branding, so you can send just the fields you want to change. Every field is normalized (unknown modes fall back to `default`, text is trimmed and truncated, `logoHeight` is clamped to 16-44 or 0).\n\nLogo and favicon files are managed with `POST`/`DELETE /admin-next/branding/logo`; the `logoLight`, `logoDark` and `favicon` fields here accept only a basename inside `user/media/admin-next/`.\n\nRequires super-admin (`admin.super`, subject to the API-key scope cap). Demo accounts are refused. Returns the full resolved payload.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"mode\": \"text\",\n  \"text\": \"Acme CMS\",\n  \"title\": \"Acme Content Admin\",\n  \"showPoweredBy\": false\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "id": "openapi:uploadAdminNextBrandingLogo",
          "name": "Upload a logo or favicon",
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "// Generated from openapi.yaml by tests/newman/sync-collection.php; the Newman suite skips it.",
                  "if (pm.variables.get('newman_suite')) { pm.execution.skipRequest(); }"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "X-API-Key",
                "value": "{{api_key}}",
                "description": "API Key authentication"
              },
              {
                "key": "X-Grav-Environment",
                "value": "{{grav_environment}}",
                "description": "Grav environment"
              }
            ],
            "url": {
              "raw": "{{base_url}}{{api_prefix}}/admin-next/branding/logo",
              "host": [
                "{{base_url}}{{api_prefix}}"
              ],
              "path": [
                "admin-next",
                "branding",
                "logo"
              ],
              "query": [
                {
                  "key": "variant",
                  "value": "light",
                  "description": "Which branding file to act on. Required unless sent as a `variant` form/body field instead.",
                  "disabled": true
                }
              ]
            },
            "description": "Uploads a light logo, dark logo, or favicon to `user/media/admin-next/` and records it in the site branding. The file type is determined from the file contents, never the client-declared MIME type: PNG, JPEG, WebP and ICO are accepted as raster images; anything else must parse as an SVG document and is sanitized (script and foreignObject elements, `on*` attributes, and `javascript:`/`data:` URIs are removed). Maximum size is 4 MB.\n\nThe stored file is named `logo-{variant}-{hash}.{ext}` (or `favicon-favicon-{hash}.{ext}`), and the previous file for that variant is deleted. Uploading a light or dark logo switches the branding mode to `custom` unless it is currently `text`.\n\nRequires super-admin (`admin.super`, subject to the API-key scope cap). Demo accounts are refused.",
            "body": {
              "mode": "formdata",
              "formdata": [
                {
                  "key": "file",
                  "type": "file",
                  "src": [],
                  "description": "The image file. The field may also be named `logo`."
                },
                {
                  "key": "variant",
                  "type": "text",
                  "value": "",
                  "description": "Alternative to the `variant` query parameter; the query parameter wins when both are sent."
                }
              ]
            }
          }
        },
        {
          "id": "openapi:deleteAdminNextBrandingLogo",
          "name": "Delete a logo or favicon",
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "// Generated from openapi.yaml by tests/newman/sync-collection.php; the Newman suite skips it.",
                  "if (pm.variables.get('newman_suite')) { pm.execution.skipRequest(); }"
                ]
              }
            }
          ],
          "request": {
            "method": "DELETE",
            "header": [
              {
                "key": "X-API-Key",
                "value": "{{api_key}}",
                "description": "API Key authentication"
              },
              {
                "key": "X-Grav-Environment",
                "value": "{{grav_environment}}",
                "description": "Grav environment"
              }
            ],
            "url": {
              "raw": "{{base_url}}{{api_prefix}}/admin-next/branding/logo",
              "host": [
                "{{base_url}}{{api_prefix}}"
              ],
              "path": [
                "admin-next",
                "branding",
                "logo"
              ],
              "query": [
                {
                  "key": "variant",
                  "value": "light",
                  "description": "Which branding file to act on. Required unless sent as a `variant` form/body field instead.",
                  "disabled": true
                }
              ]
            },
            "description": "Deletes the stored file for one branding variant and clears its path in the site branding. If both logo variants end up empty while the mode is `custom`, the mode reverts to `default` so Admin Next shows the built-in Grav logo. Succeeds even when no file was set for the variant.\n\nRequires super-admin (`admin.super`, subject to the API-key scope cap). Demo accounts are refused. Returns the full resolved payload."
          }
        }
      ]
    },
    {
      "name": "UI Extensions",
      "item": [
        {
          "id": "openapi:getCustomFieldRegistry",
          "name": "List custom field types provided by plugins and themes",
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "// Generated from openapi.yaml by tests/newman/sync-collection.php; the Newman suite skips it.",
                  "if (pm.variables.get('newman_suite')) { pm.execution.skipRequest(); }"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "X-API-Key",
                "value": "{{api_key}}",
                "description": "API Key authentication"
              },
              {
                "key": "X-Grav-Environment",
                "value": "{{grav_environment}}",
                "description": "Grav environment"
              }
            ],
            "url": {
              "raw": "{{base_url}}{{api_prefix}}/custom-fields",
              "host": [
                "{{base_url}}{{api_prefix}}"
              ],
              "path": [
                "custom-fields"
              ]
            },
            "description": "Returns a map of custom blueprint field type to the package that provides its Admin Next web component, so Admin Next can fill its field registry at startup. Enabled plugins and all installed themes are scanned for `admin-next/fields/*.js`. When two packages ship the same type, the later one wins (themes are scanned after plugins).\n\nUse `kind` and `slug` to build the script URL: `/gpm/{kind}/{slug}/field/{type}` or the bundle at `/gpm/{kind}/{slug}/fields`. When nothing is registered the map is serialized as an empty array (`[]`).\n\nRequires `api.access`, the same as the field scripts it points to: Admin Next loads it at startup for every account, including editors who cannot manage packages."
          }
        },
        {
          "id": "openapi:getEditorToolbarButtons",
          "name": "List markdown editor toolbar buttons contributed by plugins",
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "// Generated from openapi.yaml by tests/newman/sync-collection.php; the Newman suite skips it.",
                  "if (pm.variables.get('newman_suite')) { pm.execution.skipRequest(); }"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "X-API-Key",
                "value": "{{api_key}}",
                "description": "API Key authentication"
              },
              {
                "key": "X-Grav-Environment",
                "value": "{{grav_environment}}",
                "description": "Grav environment"
              }
            ],
            "url": {
              "raw": "{{base_url}}{{api_prefix}}/editor/toolbar-buttons",
              "host": [
                "{{base_url}}{{api_prefix}}"
              ],
              "path": [
                "editor",
                "toolbar-buttons"
              ]
            },
            "description": "Collects buttons that plugins add to the Admin Next default (CodeMirror) markdown editor toolbar through the `onApiMarkdownEditorButtons` event. Buttons whose `authorize` permission the user lacks are removed, and the `authorize` field itself is stripped from the output. Buttons without `authorize` are visible to every authenticated user. The list is returned in event order.\n\nRequires `api.access`."
          }
        },
        {
          "id": "openapi:getFloatingWidgets",
          "name": "List floating widgets contributed by plugins",
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "// Generated from openapi.yaml by tests/newman/sync-collection.php; the Newman suite skips it.",
                  "if (pm.variables.get('newman_suite')) { pm.execution.skipRequest(); }"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "X-API-Key",
                "value": "{{api_key}}",
                "description": "API Key authentication"
              },
              {
                "key": "X-Grav-Environment",
                "value": "{{grav_environment}}",
                "description": "Grav environment"
              }
            ],
            "url": {
              "raw": "{{base_url}}{{api_prefix}}/floating-widgets",
              "host": [
                "{{base_url}}{{api_prefix}}"
              ],
              "path": [
                "floating-widgets"
              ]
            },
            "description": "Collects persistent floating widgets (chat assistants, notification panels and the like) that plugins register for the Admin Next shell through the `onApiFloatingWidgets` event. Widgets whose `authorize` permission the user lacks are removed and `authorize` is stripped. `routes` is normalized to a de-duplicated list of leading-slash paths and dropped when empty. The list is returned in event order.\n\nThe widget's web component is loaded from `GET /gpm/plugins/{plugin}/widget-script`. Requires `api.access`."
          }
        },
        {
          "id": "openapi:getContextPanels",
          "name": "List page-editor context panels contributed by plugins",
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "// Generated from openapi.yaml by tests/newman/sync-collection.php; the Newman suite skips it.",
                  "if (pm.variables.get('newman_suite')) { pm.execution.skipRequest(); }"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "X-API-Key",
                "value": "{{api_key}}",
                "description": "API Key authentication"
              },
              {
                "key": "X-Grav-Environment",
                "value": "{{grav_environment}}",
                "description": "Grav environment"
              }
            ],
            "url": {
              "raw": "{{base_url}}{{api_prefix}}/context-panels",
              "host": [
                "{{base_url}}{{api_prefix}}"
              ],
              "path": [
                "context-panels"
              ]
            },
            "description": "Collects slide-in panels that plugins register for the Admin Next page editor through the `onApiContextPanels` event. Each panel adds a trigger button to the editor toolbar in the listed `contexts`, and its web component is loaded from `GET /gpm/plugins/{plugin}/panel-script`.\n\nA panel may declare `authorize` (a permission string, or an array where any one grants access); panels the caller doesn't pass are left out, and `authorize` is stripped from the response. The list is sorted by `priority`, highest first, keeping registration order among equal priorities. Requires `api.access`."
          }
        },
        {
          "id": "openapi:getSettingsPanels",
          "name": "List Settings page panels contributed by plugins",
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "// Generated from openapi.yaml by tests/newman/sync-collection.php; the Newman suite skips it.",
                  "if (pm.variables.get('newman_suite')) { pm.execution.skipRequest(); }"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "X-API-Key",
                "value": "{{api_key}}",
                "description": "API Key authentication"
              },
              {
                "key": "X-Grav-Environment",
                "value": "{{grav_environment}}",
                "description": "Grav environment"
              }
            ],
            "url": {
              "raw": "{{base_url}}{{api_prefix}}/settings/panels",
              "host": [
                "{{base_url}}{{api_prefix}}"
              ],
              "path": [
                "settings",
                "panels"
              ]
            },
            "description": "Collects blueprint-driven panels that plugins register through the `onApiAdminSettingsPanels` event. Admin Next renders them as sections inside its Settings page instead of as separate sidebar entries. Panels are sorted by `priority`, highest first. No permission filtering happens here; the registering plugin decides, using the `user` passed in the event, whether to add a panel.\n\nRequires `api.access`."
          }
        }
      ]
    },
    {
      "name": "Teardown",
      "description": "Teardown requests that run after all other tests. Cleans up test data.",
      "item": [
        {
          "name": "Teardown: Delete Test Page",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"Status is 204 or 404\", () => {",
                  "    pm.expect(pm.response.code).to.be.oneOf([204, 404]);",
                  "});"
                ]
              }
            }
          ],
          "request": {
            "method": "DELETE",
            "header": [
              {
                "key": "X-API-Key",
                "value": "{{api_key}}",
                "description": "API Key authentication"
              },
              {
                "key": "X-Grav-Environment",
                "value": "{{grav_environment}}",
                "description": "Grav environment"
              }
            ],
            "url": {
              "raw": "{{base_url}}{{api_prefix}}/pages/newman-test-page?children=true",
              "host": [
                "{{base_url}}{{api_prefix}}"
              ],
              "path": [
                "pages",
                "test",
                "newman-page"
              ],
              "query": [
                {
                  "key": "children",
                  "value": "true"
                }
              ]
            },
            "description": "Delete the test page created during setup."
          }
        },
        {
          "name": "Teardown: Delete Test Webhook",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"Status is 204 or 404\", () => {",
                  "    pm.expect(pm.response.code).to.be.oneOf([204, 404]);",
                  "});"
                ]
              }
            }
          ],
          "request": {
            "method": "DELETE",
            "header": [
              {
                "key": "X-API-Key",
                "value": "{{api_key}}",
                "description": "API Key authentication"
              },
              {
                "key": "X-Grav-Environment",
                "value": "{{grav_environment}}",
                "description": "Grav environment"
              }
            ],
            "url": {
              "raw": "{{base_url}}{{api_prefix}}/webhooks/{{webhook_id}}",
              "host": [
                "{{base_url}}{{api_prefix}}"
              ],
              "path": [
                "webhooks",
                "{{webhook_id}}"
              ]
            },
            "description": "Delete the test webhook created during setup."
          }
        },
        {
          "name": "Teardown: Revoke Token",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"Status is 204 or 401\", () => {",
                  "    pm.expect(pm.response.code).to.be.oneOf([204, 401]);",
                  "});"
                ]
              }
            }
          ],
          "request": {
            "auth": {
              "type": "noauth"
            },
            "method": "POST",
            "header": [
              {
                "key": "X-Grav-Environment",
                "value": "{{grav_environment}}",
                "description": "Grav environment"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n    \"refresh_token\": \"{{refresh_token}}\"\n}"
            },
            "url": {
              "raw": "{{base_url}}{{api_prefix}}/auth/revoke",
              "host": [
                "{{base_url}}{{api_prefix}}"
              ],
              "path": [
                "auth",
                "revoke"
              ]
            },
            "description": "Revoke the refresh token obtained during setup."
          }
        }
      ]
    }
  ]
}
