{"id":15724,"date":"2026-09-06T11:09:47","date_gmt":"2026-09-06T09:09:47","guid":{"rendered":"https:\/\/www.lukaswojcik.com\/blog\/toolbox\/wordpress-rest-route-inventory\/"},"modified":"2026-09-06T11:09:47","modified_gmt":"2026-09-06T09:09:47","slug":"wordpress-rest-route-inventory","status":"publish","type":"page","link":"https:\/\/www.lukaswojcik.com\/blog\/en\/toolbox\/wordpress-rest-route-inventory\/","title":{"rendered":"WordPress REST Route Inventory: what a site hands out without a login"},"content":{"rendered":"<div class=\"gtm-analyser-container\" style=\"background: var(--bg-panel, #1e1e24); padding: 25px; border-radius: 8px; border: 1px solid var(--border, #2a2a35);\">\n<p style=\"color: var(--text-secondary, #a0a0b0); margin-bottom: 20px;\">The directory under \/wp-json\/ lists every registered route, including those of every plugin. It says nothing about permissions: the check sits inside the route and runs only when the route is called. This tool therefore does both \u2014 it reads the directory and then requests ten known routes without credentials.<\/p>\n<div style=\"margin-bottom: 14px;\">\n        <label for=\"rs-url\" style=\"color: var(--text-secondary, #a0a0b0); display: block; font-size: 0.85rem; margin-bottom: 5px;\">Address of the WordPress installation<\/label><br \/>\n        <input id=\"rs-url\" type=\"text\" value=\"https:\/\/wordpress.org\/news\" class=\"form-control\" style=\"width: 100%; padding: 10px; background: var(--bg-body, #14141a); border: 1px solid var(--border, #2a2a35); color: var(--text-primary, #e8e8ee); border-radius: 6px; box-sizing: border-box; font-family: monospace;\"><\/p>\n<p style=\"color: var(--text-secondary, #a0a0b0); font-size: 0.78rem; margin: 5px 0 0;\">The address the site runs under, including a subdirectory such as \/blog if there is one.<\/p>\n<\/p><\/div>\n<p style=\"color: var(--text-secondary, #a0a0b0); font-size: 0.82rem; margin-bottom: 16px;\">Eleven read requests are sent from this server: the directory and ten known routes. Nothing is written and no login is attempted. Only the query itself is stored, for the hourly limit.<\/p>\n<p>    <button id=\"rs-btn\" class=\"button\" style=\"background: var(--accent, #7ee787); color: var(--on-accent, #0b1114); border: none; padding: 12px 24px; border-radius: 6px; font-weight: 700; cursor: pointer;\">Read routes<\/button><\/p>\n<div id=\"rs-ausgabe\" style=\"margin-top: 22px;\"><\/div>\n<p style=\"color: var(--text-secondary, #a0a0b0); font-size: 0.8rem; margin: 24px 0 0;\">Five queries per hour. Only GET requests to standard addresses, private address ranges are refused.<\/p>\n<\/div>\n<p><script>\n(function () {\n    'use strict';<\/p>\n<p>    const T = {\"einleitung\":\"The directory under \\\/wp-json\\\/ lists every registered route, including those of every plugin. It says nothing about permissions: the check sits inside the route and runs only when the route is called. This tool therefore does both \u2014 it reads the directory and then requests ten known routes without credentials.\",\"label_url\":\"Address of the WordPress installation\",\"hinweis_url\":\"The address the site runs under, including a subdirectory such as \\\/blog if there is one.\",\"datenschutz\":\"Eleven read requests are sent from this server: the directory and ten known routes. Nothing is written and no login is attempted. Only the query itself is stored, for the hourly limit.\",\"knopf\":\"Read routes\",\"grenze\":\"Five queries per hour. Only GET requests to standard addresses, private address ranges are refused.\",\"laeuft\":\"Reading the directory and requesting the sample routes \u2026\",\"urteil_gut\":\"Nothing stands in the way\",\"urteil_warnung\":\"Worth a look\",\"urteil_kritisch\":\"Needs attention\",\"zusammenfassung\":\"{routen} routes in {raeume} namespaces, {schreibend} of them accept writing methods.\",\"kein_verzeichnis_text\":\"No route directory could be read at this address.\",\"h_proben\":\"The sample, requested without a login\",\"proben_hinweis\":\"Open means the route answered with 200 and content. Protected means the permission check refused with 401 or 403. Absent means 404.\",\"h_raeume\":\"Namespaces\",\"h_seite\":\"What the directory says about the site\",\"h_befunde\":\"Findings\",\"spalte_route\":\"Route\",\"spalte_zustand\":\"State\",\"spalte_status\":\"Status\",\"spalte_anzahl\":\"Entries\",\"spalte_raum\":\"Namespace\",\"spalte_routen\":\"Routes\",\"zustand_offen\":\"open\",\"zustand_geschuetzt\":\"protected\",\"zustand_fehlt\":\"absent\",\"zustand_gesperrt\":\"blocked\",\"zustand_fehler\":\"error\",\"zeile_name\":\"Name\",\"zeile_beschreibung\":\"Description\",\"zeile_sprache\":\"Language\",\"zeile_zeitzone\":\"Time zone\",\"zeile_anmeldung\":\"Authentication methods\",\"zeile_weg\":\"Route form\",\"zeile_dauer\":\"Duration\",\"zeile_limit\":\"Queries left this hour\",\"keine_anmeldung\":\"none declared\",\"weg_wp_json\":\"\\\/wp-json\\\/ (pretty permalinks)\",\"weg_rest_route\":\"?rest_route= (plain permalinks)\",\"keine_befunde\":\"No findings.\",\"stufe_hoch\":\"High\",\"stufe_mittel\":\"Medium\",\"stufe_niedrig\":\"Low\",\"stufe_info\":\"Note\",\"stufe_gut\":\"Good\",\"befund_kein_verzeichnis\":\"The directory could not be fetched || The request failed with: {fehler}.\",\"befund_rest_abgeschaltet\":\"The route directory answers 404 || Either the REST API is switched off or something in front of it blocks the address.\",\"befund_rest_gesperrt\":\"The route directory is protected || Status {status}: reading the directory already requires a login.\",\"befund_kein_json\":\"No JSON at the directory address || Status {status}, content type {typ}. Something answers there, but not the REST API.\",\"befund_json_unlesbar\":\"The directory is not readable JSON || The answer arrived but could not be parsed.\",\"befund_benutzer_offen\":\"The user list is open || \\\/wp\\\/v2\\\/users answered with {anzahl} entries. Names and login slugs are the first half of a login attempt, and this route hands them out without any credentials.\",\"befund_benutzer_zu\":\"The user list is not open || Status {status}. Either the route is switched off or the permission check refuses it.\",\"befund_route_offen_settings\":\"The settings are readable || {pfad} answered with 200. This route requires administrator rights in a default installation.\",\"befund_route_offen_plugins\":\"The plugin list is readable || {pfad} answered with 200. Version numbers of installed plugins are the shortest path to a matching exploit.\",\"befund_route_offen_themes\":\"The theme list is readable || {pfad} answered with 200. In a default installation this route is not public.\",\"befund_inhalte_offen\":\"Posts are readable || {anzahl} entries at \\\/wp\\\/v2\\\/posts. That is the intended behaviour for published content.\",\"befund_verzeichnis_offen\":\"The directory itself is readable || {routen} routes in {raeume} namespaces. This is the default, and it is also a complete list of the plugins that register routes.\",\"befund_schreibende_routen\":\"{anzahl} routes accept writing methods || Declared in the directory. Whether they permit anything is decided by their own permission check, not by this list.\",\"befund_fremde_namensraeume\":\"{anzahl} namespaces beyond the WordPress core || For instance {raeume}. Each one comes from a plugin and names it.\",\"befund_ohne_huebsche_adressen\":\"The directory only answers at ?rest_route= || Pretty permalinks are switched off, so the REST API is reached through the query string.\",\"befund_seitendaten\":\"Site name and description are in the directory || That is intended and public, and it also shows up in the directory of a site that otherwise reveals nothing.\",\"fehler_adresse\":\"That address cannot be used.\",\"fehler_netz\":\"The server could not be reached.\",\"fehler_antwort\":\"The answer was not readable.\",\"fehler_captcha\":\"The spam check failed. A reload usually helps.\",\"fehler_kein_token\":\"The spam check did not produce a token.\",\"fehler_zu_viele\":\"Hourly limit of {limit} queries reached.\",\"fehler_zaehler\":\"The counter is unavailable.\",\"fehler_pruefdienst\":\"The spam check is unavailable.\",\"fehler_aufbau\":\"The tool is misconfigured.\",\"fehler_methode\":\"Wrong request method.\",\"fehler_eingabe\":\"The input was not readable.\",\"fehler_unbekannt\":\"Something went wrong.\",\"grund_leer\":\"No address was given.\",\"grund_zu_lang\":\"The address is too long.\",\"grund_schema\":\"Only http and https work.\",\"grund_unlesbar\":\"The address could not be parsed.\",\"grund_benutzerinfo\":\"Addresses with a user name are refused.\",\"grund_gesperrter_bereich\":\"That address points into a private network.\",\"grund_kein_punkt\":\"A host name needs a dot.\",\"grund_hostform\":\"A domain name is required, not an IP address.\",\"grund_port\":\"That port is not allowed.\",\"grund_kein_dns\":\"The name does not resolve.\"};<\/p>\n<p>    var ENDPUNKT = '\/lw-restrouten.php';\n    var SITEKEY = '6LcrPkEtAAAAAPo1QCOf-IIM2fCL0UfdJz4y2iSY';\n    var STUFEN = ['hoch', 'mittel', 'niedrig', 'info', 'gut'];\n    var ZUSTAENDE = ['offen', 'geschuetzt', 'fehlt', 'gesperrt', 'fehler'];\n    var PROBEN = ['users', 'settings', 'plugins', 'themes', 'posts', 'pages', 'media', 'comments', 'types', 'search'];<\/p>\n<p>    function liste(w) { return Array.isArray(w) ? w : []; }\n    function zahl(w) { return (typeof w === 'number' && isFinite(w)) ? w : null; }\n    function text(schluessel, daten) {\n        var t = T[schluessel];\n        if (typeof t !== 'string') { return null; }\n        return t.replace(\/\\{([a-z_0-9]+)\\}\/g, function (m, k) {\n            return (daten && daten[k] !== undefined && daten[k] !== null) ? String(daten[k]) : m;\n        });\n    }<\/p>\n<p>    function bewerten(antwort) {\n        var a = antwort || {};\n        if (a.fehler) { return { fehler: String(a.fehler), grund: a.grund || null, limit: zahl(a.limit) }; }\n        var befunde = liste(a.befunde).map(function (f) {\n            return { key: String(f.key || ''), stufe: STUFEN.indexOf(f.stufe) === -1 ? 'info' : f.stufe, daten: (f.daten && typeof f.daten === 'object') ? f.daten : {} };\n        });\n        var gruppen = {};\n        STUFEN.forEach(function (s) { gruppen[s] = befunde.filter(function (f) { return f.stufe === s; }); });\n        var rohProben = (a.proben && typeof a.proben === 'object') ? a.proben : {};\n        var proben = [];\n        PROBEN.forEach(function (k) {\n            var p = rohProben[k];\n            if (!p) { return; }\n            proben.push({ key: k, pfad: String(p.pfad || ''), status: zahl(p.status),\n                zustand: ZUSTAENDE.indexOf(p.zustand) === -1 ? 'fehler' : p.zustand, anzahl: zahl(p.anzahl) });\n        });\n        var s = (a.seite && typeof a.seite === 'object') ? a.seite : {};\n        return {\n            fehler: null,\n            basis: a.basis ? String(a.basis) : '',\n            erreichbar: !!a.erreichbar,\n            weg: a.weg === 'rest_route' ? 'rest_route' : 'wp-json',\n            status: zahl(a.status),\n            urteil: (a.urteil === 'gut' || a.urteil === 'warnung' || a.urteil === 'kritisch') ? a.urteil : 'kritisch',\n            routen: zahl(a.routen_gesamt) || 0,\n            raeume: zahl(a.raeume_gesamt) || 0,\n            schreibend: zahl(a.schreibend) || 0,\n            namensraeume: liste(a.namensraeume).map(function (x) { x = x || {}; return { raum: String(x.raum || ''), routen: zahl(x.routen) || 0 }; }),\n            proben: proben,\n            seite: { name: s.name ? String(s.name) : null, beschreibung: s.beschreibung ? String(s.beschreibung) : null,\n                sprache: s.sprache ? String(s.sprache) : null, zeitzone: s.zeitzone ? String(s.zeitzone) : null,\n                anmeldung: liste(s.anmeldung).map(String) },\n            befunde: befunde, stufengruppen: gruppen,\n            dauer: zahl(a.dauer_ms), limitRest: zahl(a.limit_rest)\n        };\n    }<\/p>\n<p>    function skriptLaden() {\n        return new Promise(function (auf) {\n            if (window.grecaptcha) { auf(true); return; }\n            var s = document.createElement('script');\n            s.src = 'https:\/\/www.google.com\/recaptcha\/api.js?render=' + SITEKEY;\n            s.onload = function () { auf(true); }; s.onerror = function () { auf(false); };\n            document.head.appendChild(s);\n            setTimeout(function () { auf(!!window.grecaptcha); }, 8000);\n        });\n    }\n    function tokenHolen() {\n        return skriptLaden().then(function (da) {\n            if (!da || !window.grecaptcha || !window.grecaptcha.ready) { return null; }\n            return new Promise(function (auf) {\n                var fertig = false;\n                function einmal(w) { if (!fertig) { fertig = true; auf(w); } }\n                setTimeout(function () { einmal(null); }, 12000);\n                try {\n                    window.grecaptcha.ready(function () {\n                        if (fertig) { return; }\n                        if (!window.grecaptcha.execute) { einmal(null); return; }\n                        window.grecaptcha.execute(SITEKEY, { action: 'restrouten' }).then(function (t) { einmal(t || null); }, function () { einmal(null); });\n                    });\n                } catch (e) { einmal(null); }\n            });\n        }, function () { return null; });\n    }\n    function abfragen(url) {\n        return tokenHolen().then(function (token) {\n            return fetch(ENDPUNKT, { method: 'POST', headers: { 'Content-Type': 'application\/json' }, body: JSON.stringify({ url: url, token: token || '' }) })\n                .then(function (r) { return r.json().then(function (j) { return j; }, function () { return { fehler: 'antwort' }; }); }, function () { return { fehler: 'netz' }; });\n        });\n    }<\/p>\n<p>    window.LW_TEST = window.LW_TEST || {};\n    window.LW_TEST.RS = { bewerten: bewerten, darstellen: null, text: text,\n        ENDPUNKT: ENDPUNKT, STUFEN: STUFEN, ZUSTAENDE: ZUSTAENDE, PROBEN: PROBEN, T: T };<\/p>\n<p>    var btn = document.getElementById('rs-btn');\n    var out = document.getElementById('rs-ausgabe');\n    if (!btn || !out) { return; }<\/p>\n<p>    function el(tag, stil, txt) { var e = document.createElement(tag); if (stil) { e.setAttribute('style', stil); } if (txt !== undefined && txt !== null) { e.textContent = txt; } return e; }\n    function wert(id) { var e = document.getElementById(id); return e ? e.value : ''; }\n    var UEBERSCHRIFT = 'font-family: \"Nunito Sans\", sans-serif; font-weight: 700; color: var(--text-primary, #e8e8ee); font-size: 0.95rem; margin: 22px 0 8px;';\n    var ZELLE = 'padding: 5px 12px 5px 0; color: var(--text-secondary, #a0a0b0); font-size: 0.87rem;';\n    var ZELLE_WERT = 'padding: 5px 12px 5px 0; color: var(--text-primary, #e8e8ee); font-size: 0.87rem; font-family: monospace; word-break: break-all;';\n    var KASTEN = 'background: var(--bg-body, #14141a); border: 1px solid var(--border, #2a2a35); border-radius: 8px; padding: 14px 16px; margin: 0 0 10px;';\n    var GUT = ' color: #7ee787;', WARN = ' color: #ffa94d;', ROT = ' color: #ff7b72;', GRAU = ' color: #8a8a99;';\n    var FARBEN = { hoch: '#ff7b72', mittel: '#ffa94d', niedrig: '#e3b341', info: '#8a8a99', gut: '#7ee787' };\n    var URTEIL = { gut: GUT, warnung: WARN, kritisch: ROT };\n    var ZUSTAND_FARBE = { offen: WARN, geschuetzt: GUT, fehlt: GRAU, gesperrt: GRAU, fehler: ROT };\n    function gitter(sp) { return el('div', 'display: grid; grid-template-columns: ' + sp + '; gap: 0 18px; align-items: baseline;'); }\n    function paar(g, n, w, stil) { g.appendChild(el('div', ZELLE, n)); g.appendChild(el('div', ZELLE_WERT + (stil || ''), w)); }<\/p>\n<p>    function darstellen(b, ziel) {\n        ziel.innerHTML = '';\n        if (b.fehler) {\n            var txt = T['fehler_' + b.fehler] || T.fehler_unbekannt;\n            if (b.grund && T['grund_' + b.grund]) { txt = txt + ' ' + T['grund_' + b.grund]; }\n            if (b.limit !== null) { txt = txt.replace('{limit}', String(b.limit)); }\n            ziel.appendChild(el('p', ZELLE + WARN + ' margin: 0;', txt));\n            return;\n        }<\/p>\n<p>        ziel.appendChild(el('div', 'font-size: 1.05rem; font-weight: 700; margin: 0 0 6px;' + URTEIL[b.urteil], T['urteil_' + b.urteil]));\n        if (!b.erreichbar) {\n            ziel.appendChild(el('p', ZELLE + ' margin: 0 0 12px;', T.kein_verzeichnis_text));\n        } else {\n            ziel.appendChild(el('p', ZELLE + ' margin: 0 0 12px;', text('zusammenfassung', { routen: b.routen, raeume: b.raeume, schreibend: b.schreibend })));<\/p>\n<p>            \/\/ --- Die Stichproben: der eigentliche Kern\n            ziel.appendChild(el('div', UEBERSCHRIFT, T.h_proben));\n            ziel.appendChild(el('p', ZELLE + ' margin: 0 0 8px;', T.proben_hinweis));\n            var hp = el('div', 'overflow-x: auto;');\n            var gp = gitter('auto auto auto auto');\n            [T.spalte_route, T.spalte_zustand, T.spalte_status, T.spalte_anzahl].forEach(function (s) {\n                gp.appendChild(el('div', ZELLE + ' font-weight: 700; color: var(--text-primary, #e8e8ee); white-space: nowrap;', s));\n            });\n            b.proben.forEach(function (p) {\n                gp.appendChild(el('div', ZELLE_WERT + ' white-space: nowrap;', p.pfad));\n                gp.appendChild(el('div', ZELLE_WERT + ZUSTAND_FARBE[p.zustand] + ' white-space: nowrap;', T['zustand_' + p.zustand]));\n                gp.appendChild(el('div', ZELLE_WERT + GRAU, p.status === null ? '-' : String(p.status)));\n                gp.appendChild(el('div', ZELLE_WERT + GRAU, p.anzahl === null ? '-' : String(p.anzahl)));\n            });\n            hp.appendChild(gp);\n            ziel.appendChild(hp);<\/p>\n<p>            \/\/ --- Namensraeume\n            if (b.namensraeume.length) {\n                ziel.appendChild(el('div', UEBERSCHRIFT, T.h_raeume));\n                var hr = el('div', 'overflow-x: auto;');\n                var gr = gitter('auto auto');\n                [T.spalte_raum, T.spalte_routen].forEach(function (s) {\n                    gr.appendChild(el('div', ZELLE + ' font-weight: 700; color: var(--text-primary, #e8e8ee); white-space: nowrap;', s));\n                });\n                b.namensraeume.forEach(function (n) {\n                    gr.appendChild(el('div', ZELLE_WERT + ' white-space: nowrap;', n.raum));\n                    gr.appendChild(el('div', ZELLE_WERT + GRAU, String(n.routen)));\n                });\n                hr.appendChild(gr);\n                ziel.appendChild(hr);\n            }<\/p>\n<p>            \/\/ --- Was das Verzeichnis ueber die Seite sagt\n            ziel.appendChild(el('div', UEBERSCHRIFT, T.h_seite));\n            var gs = gitter('auto auto');\n            paar(gs, T.zeile_name, b.seite.name || '-', GRAU);\n            paar(gs, T.zeile_beschreibung, b.seite.beschreibung || '-', GRAU);\n            paar(gs, T.zeile_sprache, b.seite.sprache || '-', GRAU);\n            paar(gs, T.zeile_zeitzone, b.seite.zeitzone || '-', GRAU);\n            paar(gs, T.zeile_anmeldung, b.seite.anmeldung.length ? b.seite.anmeldung.join(', ') : T.keine_anmeldung, GRAU);\n            paar(gs, T.zeile_weg, b.weg === 'rest_route' ? T.weg_rest_route : T.weg_wp_json, GRAU);\n            if (b.dauer !== null) { paar(gs, T.zeile_dauer, b.dauer + ' ms', GRAU); }\n            if (b.limitRest !== null) { paar(gs, T.zeile_limit, String(b.limitRest), GRAU); }\n            ziel.appendChild(gs);\n        }<\/p>\n<p>        \/\/ --- Befunde\n        ziel.appendChild(el('div', UEBERSCHRIFT, T.h_befunde));\n        var irgendwas = false;\n        STUFEN.forEach(function (s) {\n            var g2 = b.stufengruppen[s];\n            if (!g2.length) { return; }\n            irgendwas = true;\n            ziel.appendChild(el('div', 'font-weight: 700; font-size: 0.82rem; text-transform: uppercase; letter-spacing: 0.5px; margin: 14px 0 6px; color: ' + FARBEN[s] + ';', T['stufe_' + s] + ' (' + g2.length + ')'));\n            g2.forEach(function (f) {\n                var k = el('div', KASTEN + ' border-left: 3px solid ' + FARBEN[s] + ';');\n                var d = {};\n                Object.keys(f.daten).forEach(function (n) {\n                    var w = f.daten[n];\n                    d[n] = Array.isArray(w) ? w.join(', ') : w;\n                });\n                var t = text('befund_' + f.key, d) || f.key;\n                var teile = t.split(' || ');\n                k.appendChild(el('div', 'color: var(--text-primary, #e8e8ee); font-size: 0.9rem; font-weight: 700;', teile[0]));\n                if (teile[1]) { k.appendChild(el('div', ZELLE + ' padding: 6px 0 0; line-height: 1.55;', teile[1])); }\n                ziel.appendChild(k);\n            });\n        });\n        if (!irgendwas) { ziel.appendChild(el('p', ZELLE + ' margin: 0;', T.keine_befunde)); }\n    }\n    window.LW_TEST.RS.darstellen = darstellen;<\/p>\n<p>    var laeuft = false;\n    btn.addEventListener('click', function () {\n        if (laeuft) { return; }\n        laeuft = true; btn.disabled = true;\n        out.innerHTML = '';\n        out.appendChild(el('p', ZELLE + GRAU + ' margin: 0;', T.laeuft));\n        abfragen(wert('rs-url')).then(function (a) { darstellen(bewerten(a), out); laeuft = false; btn.disabled = false; },\n            function () { darstellen(bewerten({ fehler: 'netz' }), out); laeuft = false; btn.disabled = false; });\n    });\n})();\n<\/script><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Reads the route directory under \/wp-json\/, counts routes and namespaces, and then fetches ten well-known routes without any credentials \u2014 because the directory only says which routes exist, never who is allowed to read them. Only that second step separates a listed route from an open one.<\/p>\n","protected":false},"author":1,"featured_media":0,"parent":38,"menu_order":0,"comment_status":"closed","ping_status":"closed","template":"template-tool-base.php","meta":{"footnotes":""},"tags":[92757,91115,91096],"class_list":["post-15724","page","type-page","status-publish","hentry","tag-web-development","tag-web-security","tag-wordpress"],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v28.1 - https:\/\/yoast.com\/product\/yoast-seo-wordpress\/ -->\n<title>WordPress REST Route Inventory: what a site hands out without a login - Lukas Wojcik - Blog<\/title>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/www.lukaswojcik.com\/blog\/en\/toolbox\/wordpress-rest-route-inventory\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"WordPress REST Route Inventory: what a site hands out without a login - Lukas Wojcik - Blog\" \/>\n<meta property=\"og:description\" content=\"Reads the route directory under \/wp-json\/, counts routes and namespaces, and then fetches ten well-known routes without any credentials \u2014 because the directory only says which routes exist, never who is allowed to read them. Only that second step separates a listed route from an open one.\" \/>\n<meta property=\"og:url\" content=\"https:\/\/www.lukaswojcik.com\/blog\/en\/toolbox\/wordpress-rest-route-inventory\/\" \/>\n<meta property=\"og:site_name\" content=\"Lukas Wojcik - Blog\" \/>\n<meta property=\"og:image\" content=\"https:\/\/www.lukaswojcik.com\/blog\/wp-content\/uploads\/2026\/08\/og-default.jpg\" \/>\n\t<meta property=\"og:image:width\" content=\"1200\" \/>\n\t<meta property=\"og:image:height\" content=\"630\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/jpeg\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:label1\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data1\" content=\"1 minute\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\\\/\\\/schema.org\",\"@graph\":[{\"@type\":\"WebPage\",\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/en\\\/toolbox\\\/wordpress-rest-route-inventory\\\/\",\"url\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/en\\\/toolbox\\\/wordpress-rest-route-inventory\\\/\",\"name\":\"WordPress REST Route Inventory: what a site hands out without a login - Lukas Wojcik - Blog\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/#website\"},\"datePublished\":\"2026-09-06T09:09:47+00:00\",\"breadcrumb\":{\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/en\\\/toolbox\\\/wordpress-rest-route-inventory\\\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/en\\\/toolbox\\\/wordpress-rest-route-inventory\\\/\"]}]},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/en\\\/toolbox\\\/wordpress-rest-route-inventory\\\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Toolbox\",\"item\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/en\\\/toolbox\\\/\"},{\"@type\":\"ListItem\",\"position\":3,\"name\":\"WordPress REST Route Inventory: what a site hands out without a login\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/#website\",\"url\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/\",\"name\":\"Lukas Wojcik - Blog\",\"description\":\"\",\"publisher\":{\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/#\\\/schema\\\/person\\\/895f7604f9b6b71aad9bba33af28d0f9\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":[\"Person\",\"Organization\"],\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/#\\\/schema\\\/person\\\/895f7604f9b6b71aad9bba33af28d0f9\",\"name\":\"luky\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/wp-content\\\/uploads\\\/2026\\\/07\\\/lw-x2.jpg\",\"url\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/wp-content\\\/uploads\\\/2026\\\/07\\\/lw-x2.jpg\",\"contentUrl\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/wp-content\\\/uploads\\\/2026\\\/07\\\/lw-x2.jpg\",\"width\":424,\"height\":636,\"caption\":\"luky\"},\"logo\":{\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/wp-content\\\/uploads\\\/2026\\\/07\\\/lw-x2.jpg\"},\"sameAs\":[\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\"]}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"WordPress REST Route Inventory: what a site hands out without a login - Lukas Wojcik - Blog","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/www.lukaswojcik.com\/blog\/en\/toolbox\/wordpress-rest-route-inventory\/","og_locale":"en_US","og_type":"article","og_title":"WordPress REST Route Inventory: what a site hands out without a login - Lukas Wojcik - Blog","og_description":"Reads the route directory under \/wp-json\/, counts routes and namespaces, and then fetches ten well-known routes without any credentials \u2014 because the directory only says which routes exist, never who is allowed to read them. Only that second step separates a listed route from an open one.","og_url":"https:\/\/www.lukaswojcik.com\/blog\/en\/toolbox\/wordpress-rest-route-inventory\/","og_site_name":"Lukas Wojcik - Blog","og_image":[{"width":1200,"height":630,"url":"https:\/\/www.lukaswojcik.com\/blog\/wp-content\/uploads\/2026\/08\/og-default.jpg","type":"image\/jpeg"}],"twitter_card":"summary_large_image","twitter_misc":{"Est. reading time":"1 minute"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"WebPage","@id":"https:\/\/www.lukaswojcik.com\/blog\/en\/toolbox\/wordpress-rest-route-inventory\/","url":"https:\/\/www.lukaswojcik.com\/blog\/en\/toolbox\/wordpress-rest-route-inventory\/","name":"WordPress REST Route Inventory: what a site hands out without a login - Lukas Wojcik - Blog","isPartOf":{"@id":"https:\/\/www.lukaswojcik.com\/blog\/#website"},"datePublished":"2026-09-06T09:09:47+00:00","breadcrumb":{"@id":"https:\/\/www.lukaswojcik.com\/blog\/en\/toolbox\/wordpress-rest-route-inventory\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/www.lukaswojcik.com\/blog\/en\/toolbox\/wordpress-rest-route-inventory\/"]}]},{"@type":"BreadcrumbList","@id":"https:\/\/www.lukaswojcik.com\/blog\/en\/toolbox\/wordpress-rest-route-inventory\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/www.lukaswojcik.com\/blog\/"},{"@type":"ListItem","position":2,"name":"Toolbox","item":"https:\/\/www.lukaswojcik.com\/blog\/en\/toolbox\/"},{"@type":"ListItem","position":3,"name":"WordPress REST Route Inventory: what a site hands out without a login"}]},{"@type":"WebSite","@id":"https:\/\/www.lukaswojcik.com\/blog\/#website","url":"https:\/\/www.lukaswojcik.com\/blog\/","name":"Lukas Wojcik - Blog","description":"","publisher":{"@id":"https:\/\/www.lukaswojcik.com\/blog\/#\/schema\/person\/895f7604f9b6b71aad9bba33af28d0f9"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/www.lukaswojcik.com\/blog\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":["Person","Organization"],"@id":"https:\/\/www.lukaswojcik.com\/blog\/#\/schema\/person\/895f7604f9b6b71aad9bba33af28d0f9","name":"luky","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/www.lukaswojcik.com\/blog\/wp-content\/uploads\/2026\/07\/lw-x2.jpg","url":"https:\/\/www.lukaswojcik.com\/blog\/wp-content\/uploads\/2026\/07\/lw-x2.jpg","contentUrl":"https:\/\/www.lukaswojcik.com\/blog\/wp-content\/uploads\/2026\/07\/lw-x2.jpg","width":424,"height":636,"caption":"luky"},"logo":{"@id":"https:\/\/www.lukaswojcik.com\/blog\/wp-content\/uploads\/2026\/07\/lw-x2.jpg"},"sameAs":["https:\/\/www.lukaswojcik.com\/blog"]}]}},"_links":{"self":[{"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/pages\/15724","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/comments?post=15724"}],"version-history":[{"count":0,"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/pages\/15724\/revisions"}],"up":[{"embeddable":true,"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/pages\/38"}],"wp:attachment":[{"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/media?parent=15724"}],"wp:term":[{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/tags?post=15724"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}