Theme
GraphQL API
Peacock exposes the public CDN read-surface as a read-only GraphQL endpoint. Same content as /v1/cdn/{space}/stories/{path}, but you get to query exactly the fields you need.
Endpoint
text
POST /v1/cdn/{space}/graphql
Content-Type: application/jsonAnonymous (no token). Rate-limited at throttle:600,1 (600 requests per minute per IP — same bucket as the REST CDN endpoint).
Why read-only
Mutations stay on the REST surface where the existing auth + rate-limit + audit infrastructure lives. GraphQL exists to give SDK consumers query-shape control over multiple round-trips, not as an alternative write path.
Quick example
bash
curl -X POST https://peacock-cms.webhoch.com/v1/cdn/demo/graphql \
-H 'Content-Type: application/json' \
-d '{
"query": "query($path: String!) { cdnStory(spaceSlug: \"demo\", path: $path) { uuid slug lang content } }",
"variables": { "path": "/welcome" }
}'Response:
json
{
"data": {
"cdnStory": {
"uuid": "492f1762-cfbd-496f-869f-006bcf9adacd",
"slug": "welcome",
"lang": "en",
"content": "{\"type\":\"doc\",\"blocks\":[...]}"
}
}
}content is JSON-stringified — parse it client-side with JSON.parse(...). The same blocks tree that the REST /stories/{path} endpoint returns.
Schema
The schema is intentionally tiny — one root query, one Story type.
graphql
type Query {
"Fetch the currently-published Story at a path in a Space."
cdnStory(spaceSlug: String!, path: String!, lang: String): Story
}
"A published Story served from the public CDN."
type Story {
uuid: String!
slug: String!
fullPath: String!
lang: String!
status: String!
content: String! # JSON-stringified blocks tree
updatedAt: String!
}Static schema URL (for tooling)
Runtime introspection (__schema, __type) is disabled — see Sicherheit below. To plug Peacock into editor plugins, graphql-codegen, Apollo CLI, or any tool that needs the SDL, point it at the static schema endpoint:
bash
curl https://peacock-cms.webhoch.com/v1/graphql/schema.gqlReturns text/plain SDL. Cached for 24h at the edge; regenerated on every deploy.
yaml
# graphql-config example
schema: https://peacock-cms.webhoch.com/v1/graphql/schema.gql
documents: 'src/**/*.graphql'ts
// @graphql-codegen/cli example
generates:
src/types/peacock.ts:
schema: https://peacock-cms.webhoch.com/v1/graphql/schema.gql
plugins:
- typescript
- typescript-operationsLocale resolution
The lang arg follows the same BCP-47 fallback chain as the REST endpoint. Missing translations fall back to the space-default locale — never a hard 404 for an existing path. The chosen locale is reflected in meta.served_lang when you query cdnStory { ... } via the REST endpoint; in GraphQL today, the Story.lang field carries it.
Limits
| Limit | Value | Why |
|---|---|---|
| Query length | 8 000 chars | DoS protection; legitimate queries never come close |
| Rate limit | 600/min per IP | Same as REST CDN; tracked per-IP not per-token |
| Field selection | Story scalar fields only | No nested-resolver fan-out — single SELECT per request |
| Mutations | ❌ disabled | Reads-only; use REST /v1/spaces/.../stories/... for writes |
| Introspection | ❌ disabled | See below |
Sicherheit
Introspection is OFF
Security audit 2026-05-21 (Medium #3) — runtime introspection lets attackers enumerate the full schema, which on a typical Lighthouse-PHP deployment also leaks resolver class names, DB column hints, etc. Peacock disables __schema + __type at query-validation time, and strips internal exception messages from the error response so DB paths, file names, and stack traces never reach the client.
| Vector | Schutz |
|---|---|
Schema enumeration via __schema | Blocked at validation rule — query rejected before resolver runs |
| Exception-message leak | report($e); return ['errors' => ['message' => 'execution failed']] — original exception is server-logged only |
| Octane state leak | DisableIntrospection rule is per-execution (passed via $validationRules), not via the library's process-global DocumentValidator::addRule() — verified by code-reviewer agent |
| DoS via deeply-nested queries | Schema has no nested object types; cannot exceed depth 2 |
Why not Lighthouse-PHP
Lighthouse is excellent when you have ~20+ entities and need directive-driven schema management. For Peacock's single-entity public read surface, a hand-rolled webonyx/graphql-php schema is ~50 lines and adds zero dependency weight to the framework's auto-discovery. When the Management API gets a GraphQL surface (post-Phase-9), Lighthouse becomes the right tool.
See also
/api/cdn— the REST counterpart/api/cdn-caching— ETag + edge-cache contract/api/openapi— OpenAPI 3.1 spec discovery/guide/i18n— BCP-47 fallback chain (including the never-hard-404 policy)