mxHeadless
REST API gateway for headless frontends on MODX 3. Resources, objects, OpenAPI, API keys, and OAuth

Generate types from live OpenAPI, then call mxHeadless with a typed envelope.
From a running site (preferred):
npx openapi-typescript https://example.com/api/v1/meta/openapi.json -o types/mxheadless.d.tsOr from docs/openapi.yaml in the mxHeadless repo.
GET /meta/openapi returns the same schema inside { "data": { ... } }. Point the generator at /meta/openapi.json if the tool expects a root openapi field. See Swagger and OpenAPI.
export type MxEnvelope<T> = {
data: T
meta?: {
total?: number
count?: number
limit?: number
offset?: number
has_more?: boolean
[key: string]: unknown
}
links?: {
self?: string
next?: string
prev?: string
[key: string]: string | undefined
}
}
export type MxProblem = {
type?: string
title?: string
status: number
detail?: string
instance?: string
code?: string
}async function mxGet<T>(
path: string,
query?: Record<string, string | number | boolean>,
init?: RequestInit,
): Promise<MxEnvelope<T>> {
const base = process.env.MXHEADLESS_BASE_URL!
const url = new URL(path.replace(/^\//, ''), base.endsWith('/') ? base : base + '/')
if (query) {
for (const [k, v] of Object.entries(query)) {
url.searchParams.set(k, String(v))
}
}
const res = await fetch(url, {
...init,
headers: {
Accept: 'application/json',
...(process.env.MXHEADLESS_API_KEY
? { Authorization: `Bearer ${process.env.MXHEADLESS_API_KEY}` }
: {}),
...(init?.headers || {}),
},
})
const body = await res.json()
if (!res.ok) {
throw body as MxProblem
}
return body as MxEnvelope<T>
}
type ResourceCard = {
id: number
pagetitle: string
uri: string
}
const list = await mxGet<ResourceCard[]>('/resources', {
limit: 10,
'filter[published]': 1,
fields: 'id,pagetitle,uri',
sort: '-id',
})