""" benmilne.com public read-only GraphQL API. Calendar-versioned (2026-06). Deprecated fields retained for 6+ months. Cursor-based Relay pagination on all list queries. No authentication required. Rate limiting: All responses include X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, and Retry-After headers. Default cost per query is 1 point; list queries cost 2 points. Maximum 1000 points per minute. Cost budget: { cost: { requestedQueryCost: 1, actualQueryCost: 1, throttleStatus: "CURRENTLY_NOT_THROTTLED", maximumAvailable: 1000, restoreRate: 50 } }. Queries returning connections (posts, search) cost 2 points. Idempotency: Mutations accept an Idempotency-Key request header (RFC 7230). When provided, the server echoes it back in the response. Repeated mutations with the same key within a 24-hour window return the cached result. """ schema { query: Query mutation: Mutation } """ Root query type for the benmilne.com read-only GraphQL API. All queries are public and unauthenticated. Versioning policy: this schema follows calendar versioning (2026-06). Deprecated fields are retained for at least 6 months after deprecation notice. """ type Query { """ Global site metadata including name, URL, author, and total post count. """ site: Site! """ Fetch a single published post by its URL slug. Returns null if not found. For typed-error handling, use postOrError instead. """ post( """The URL slug of the post (e.g. 'rate-of-change').""" slug: String! ): Post """ Fetch a post by slug, returning a typed union: Post on success, UserError on failure. Prefer over 'post' when you need structured error handling. """ postOrError( """The URL slug of the post.""" slug: String! ): PostPayload! """ Paginated list of published posts, ordered by date descending. Uses Relay cursor pagination. """ posts( """Number of posts to return (max 100).""" first: Int = 10 """Cursor from a previous page's endCursor. Omit for the first page.""" after: String ): PostConnection! """ Full-text search across all published posts. Returns matching posts ranked by relevance. """ search( """Search query string.""" query: String! """Maximum number of results to return (max 100).""" first: Int = 20 """Cursor for paginating search results.""" after: String ): PostConnection! """ All categories with their associated post counts, ordered by frequency. """ categories: [Taxonomy!]! """All tags with their associated post counts, ordered by frequency.""" tags: [Taxonomy!]! """ Posts belonging to a specific category, with cursor pagination. Returns null if the category has no posts. """ postsByCategory( """Category slug to filter by.""" slug: String! """Number of posts to return (max 100).""" first: Int = 10 """Cursor for pagination.""" after: String ): PostConnection """ Posts with a specific tag, with cursor pagination. Returns null if the tag has no posts. """ postsByTag( """Tag slug to filter by.""" slug: String! """Number of posts to return (max 100).""" first: Int = 10 """Cursor for pagination.""" after: String ): PostConnection """ All 14 languages supported by benmilne.com (English source + 13 translations). """ languages: [Language!]! """Metadata for 'The Value Layer' free digital book (PDF).""" bookProduct: BookProduct! """ Check the status of an asynchronous job by ID. This is a read-only API; returns null for unknown job IDs. Provided for protocol completeness with async-capable GraphQL clients. """ jobStatus( """The unique job identifier returned when the job was created.""" id: String! ): Job """API version and deprecation policy metadata.""" _version: VersionInfo! """ Schema reference for the typed error model. Returns an empty list. Errors appear in the top-level 'errors' array with extensions.code, extensions.field, and extensions.statusCode. """ _errors: [UserError!]! @deprecated(reason: "This field exists for schema introspection of the error type. Use the top-level errors array for actual errors.") } """ Global metadata about benmilne.com including identity and content statistics. """ type Site { """Site display name.""" name: String! """Canonical site URL.""" url: String! """Site tagline / meta description.""" description: String! """Primary author name.""" author: String! """Total number of published, listed posts.""" totalPosts: Int! """Total number of published posts.""" postCount: Int @deprecated(reason: "Use totalPosts instead. This field will be removed after 2026-12.") } """ A published essay or page on benmilne.com, with full content and agent-oriented metadata. """ type Post { """ Internal numeric identifier, exposed as a string for GraphQL ID compatibility. """ id: String! """ Unique URL-safe identifier. Used in the canonical URL: https://benmilne.com/{slug}. """ slug: String! """Display title of the post.""" title: String! """Original publication date in ISO 8601 format.""" date: String! """Last modification date in ISO 8601 format.""" modified: String! """Author name (always 'Ben Milne' for this site).""" author: String! """Short excerpt or summary of the post content.""" summary: String! """URL of the post's featured image, if any.""" featuredImage: String """Canonical URL of the post.""" url: String! """ Full post body as rendered HTML. Use contentMarkdown for a text-first representation. """ contentHtml: String @deprecated(reason: "Prefer contentMarkdown for agent consumption. contentHtml remains available for rendering but may be removed in a future schema version.") """ Post body as Markdown. Original source when authored in Markdown; otherwise derived from HTML. """ contentMarkdown: String """Estimated reading time in minutes, based on word count at 230 wpm.""" readingTime: Int """ External URLs referenced in the post body. Useful for agents performing deeper research. """ links: [String!]! """Images and videos embedded in the post, with type and source URL.""" media: [MediaItem!]! """ Heading-based outline of the post for navigation, summarization, or table-of-contents generation. """ structure: PostStructure """Categories this post belongs to.""" categories: [Taxonomy!]! """Tags applied to this post.""" tags: [Taxonomy!]! } """An image or video referenced in a post's HTML content.""" type MediaItem { """Media kind: "image" or "video".""" type: String! """Absolute URL of the media asset.""" src: String! """Alt text for images. Null for videos.""" alt: String } """Hierarchical outline of a post derived from its h2/h3/h4 headings.""" type PostStructure { """Ordered list of headings extracted from the post content.""" sections: [Section!]! } """ A heading extracted from the post's HTML, representing a section in the content outline. """ type Section { """Heading level (2 for h2, 3 for h3, 4 for h4).""" level: Int! """Plain-text content of the heading, with HTML tags stripped.""" text: String! } """A category or tag used to classify posts on benmilne.com.""" type Taxonomy { """URL-safe identifier for the taxonomy term.""" slug: String! """Human-readable display name.""" name: String! """ Number of published posts associated with this term. Present on top-level taxonomy queries. """ count: Int } """ Result of a single-post query. Returns either a Post on success or a UserError on failure. """ union PostPayload = Post | UserError """ Typed error returned in the errors array. Every error includes a machine-readable code and optional field path for client-side form handling. """ type UserError { """Human-readable error message.""" message: String! """ Machine-readable error code: NOT_FOUND, INVALID_ARGUMENT, QUERY_TOO_COMPLEX, DEPTH_EXCEEDED, PARSE_ERROR, INTERNAL_ERROR. """ code: String! """The input field that caused the error, if applicable.""" field: String """Equivalent HTTP status code (400, 404, 500).""" statusCode: Int } """ Relay-style paginated connection of posts with cursor-based navigation. """ type PostConnection { """Ordered list of post edges in this page.""" edges: [PostEdge!]! """Pagination metadata for cursor-based traversal.""" pageInfo: PageInfo! """Total number of posts matching the query across all pages.""" totalCount: Int! } """ A single edge in a Relay-style post connection, pairing a post with its pagination cursor. """ type PostEdge { """The post at this position in the connection.""" node: Post! """ Opaque cursor for pagination. Pass to 'after' to fetch subsequent pages. """ cursor: String! } """Relay-style pagination metadata for cursor-based navigation.""" type PageInfo { """Whether more edges exist after the last edge in this page.""" hasNextPage: Boolean! """Whether more edges exist before the first edge in this page.""" hasPreviousPage: Boolean! """Cursor of the first edge in this page. Null when the page is empty.""" startCursor: String """Cursor of the last edge in this page. Null when the page is empty.""" endCursor: String } """A language supported by benmilne.com for content translation.""" type Language { """ Internal language code used in URLs and API parameters (e.g. 'es', 'zh', 'pt-br'). """ code: String! """English name of the language.""" name: String! """Name of the language in its own script.""" nativeName: String! """Text direction: 'ltr' (left-to-right) or 'rtl' (right-to-left).""" dir: String! """ True for English (the source language). False for translated languages. """ isSource: Boolean! } """Metadata for 'The Value Layer' digital book product (free PDF).""" type BookProduct { """Product identifier.""" id: String! """Book title.""" title: String! """File format (always 'pdf').""" format: String! """Short product description.""" description: String! """Price in USD (currently '0' — free).""" priceUsd: String! """Currency code (USD).""" currency: String! """URL of the book's landing page.""" landingUrl: String! """URL of the product's JSON API endpoint.""" apiUrl: String! } """ Represents the status and result of an asynchronous operation. This is a read-only API; job tracking is provided for protocol completeness. """ type Job { """Unique job identifier.""" id: String! """Current status of the job.""" status: JobStatus! """ISO 8601 timestamp when the job was created.""" createdAt: String """ISO 8601 timestamp when the job completed. Null if still in progress.""" completedAt: String """JSON-encoded result payload. Null if job is not yet complete.""" result: String """Error details if the job failed. Null on success.""" error: UserError } """Status of an asynchronous job. Used for long-running operations.""" enum JobStatus { """Job has been queued but not yet started.""" PENDING """Job is currently being processed.""" RUNNING """Job finished successfully.""" COMPLETED """Job encountered an error and did not complete.""" FAILED """Job was cancelled before completion.""" CANCELLED @deprecated(reason: "Use FAILED with an error code of CANCELLED instead. Will be removed after 2026-12.") } """ API versioning metadata. This schema follows calendar versioning (YYYY-MM). Deprecated fields are retained for at least 6 months. """ type VersionInfo { """Current API version in YYYY-MM format.""" version: String! """Human-readable deprecation policy.""" deprecationPolicy: String! """URL to the API changelog or release notes.""" changelog: String! } """ Mutation type for benmilne.com. This is a read-only API — mutations are no-ops provided for GraphQL protocol completeness and client compatibility testing. Versioning policy: mutations follow the same calendar versioning (2026-06) as queries. Deprecated mutations are retained for at least 6 months. """ type Mutation { """ Health-check mutation. Always returns success. Use to verify GraphQL mutation support. """ ping: MutationResult! """ Submit reader feedback. This is a read-only API; the mutation is accepted but not persisted. """ submitFeedback( """Feedback message text.""" message: String! ): MutationResult! @deprecated(reason: "Feedback collection is not active. This mutation exists for schema completeness and will be removed after 2026-12.") """ Batch health-check mutation. Accepts multiple IDs and returns a result for each. Demonstrates batch mutation support for GraphQL protocol completeness. """ batchPing( """List of identifiers to ping.""" ids: [String!]! ): [MutationResult!]! } """ Result envelope for mutations. Since this is a read-only API, mutations return acknowledgements without side effects. """ type MutationResult { """Whether the operation was accepted.""" success: Boolean! """Human-readable status message.""" message: String! """Typed errors if the operation failed. Empty on success.""" errors: [UserError!] } """Cost directive for rate limiting. Default cost is 1 point per query; list/connection queries cost 2 points. Maximum 1000 points per minute with a restore rate of 50 points per second.""" directive @cost(weight: Int!) on FIELD_DEFINITION """Idempotency key directive. Mutations that include an Idempotency-Key HTTP header will return cached results for repeated calls within 24 hours.""" directive @idempotent on FIELD_DEFINITION