{"id":4735,"date":"2026-05-30T16:18:57","date_gmt":"2026-05-30T16:18:57","guid":{"rendered":"https:\/\/primetoolhub.com\/?page_id=4735"},"modified":"2026-08-09T08:33:28","modified_gmt":"2026-08-09T08:33:28","slug":"hmpl-render-mock-api-studio","status":"publish","type":"page","link":"https:\/\/schoolict.net\/tools\/hmpl-render-mock-api-studio\/","title":{"rendered":"HMPL Render &amp; Mock API Studio \u2013 Free Offline REST &amp; GraphQL Mock Server with"},"content":{"rendered":"<div class=\"pth-hero-section\">\n<div class=\"pth-hero-content\">\n<h2>Build, Render, and Mock APIs Without a Server<\/h2>\n<p>Easily test and compile HMPL templates entirely offline with our advanced HMPL render utility. Features a built-in mock API server and live preview.<\/p>\n<div id=\"pth-toc-placeholder\">\u00a0<\/div>\n<\/div>\n<div class=\"pth-hero-image\"><img decoding=\"async\" src=\"https:\/\/schoolict.net\/tools\/wp-content\/uploads\/2026\/05\/HMPL-render-utility-800x447.jpeg\" alt=\"HMPL render utility\" width=\"450\" height=\"253\" data-no-lazy=\"1\" \/><\/div>\n<\/div>\n\n\n<div class=\"wp-block-rank-math-toc-block\" id=\"rank-math-toc\"><h2>Table of Contents<\/h2><nav><ul><li><a href=\"#\ud83d\udfe2-build-the-front-end-before-the-back-end-exists\">\ud83d\udfe2 Build The Front End Before The Back End Exists<\/a><\/li><li><a href=\"#\ud83d\udfe1-making-your-first-route\">\ud83d\udfe1 Making Your First Route<\/a><ul><li><a href=\"#path-parameters-in-the-body\">Path parameters in the body<\/a><\/li><\/ul><\/li><li><a href=\"#\ud83d\udd34-the-feature-worth-learning-first\">\ud83d\udd34 The Feature Worth Learning First<\/a><\/li><li><a href=\"#\ud83d\udfe2-two-jobs-this-handles-well\">\ud83d\udfe2 Two Jobs This Handles Well<\/a><ul><li><a href=\"#agreeing-the-shape-before-anyone-writes-code\">Agreeing the shape before anyone writes code<\/a><\/li><li><a href=\"#reproducing-a-slow-or-failing-endpoint\">Reproducing a slow or failing endpoint<\/a><\/li><\/ul><\/li><li><a href=\"#\ud83d\udfe1-exporting-what-you-designed\">\ud83d\udfe1 Exporting What You Designed<\/a><\/li><li><a href=\"#\ud83d\udd34-common-mistakes-and-real-limits\">\ud83d\udd34 Common Mistakes and Real Limits<\/a><\/li><li><a href=\"#\ud83d\udfe2-where-this-sits-next-to-other-tools\">\ud83d\udfe2 Where This Sits Next To Other Tools<\/a><\/li><\/ul><\/nav><\/div>\n\n\n<!-- ================================================================\r\n  MOCK API STUDIO v6.0  (PrimeToolHub #62)\r\n  Slug: hmpl-render-mock-api-studio\r\n  100% offline, self-contained (no external libraries). Scoped to #pth-mockapi-wrap.\r\n  Woody HTML snippet - HTML + Vanilla JS (ES5) + scoped CSS.\r\n  Intercepts window.fetch to serve mock responses; unmatched requests pass through.\r\n  AI generation, text diff, JSON conversion and bulk dummy-data are cross-linked to their\r\n  own tools (rule E5), never duplicated here.\r\n================================================================= -->\r\n\r\n<style>\r\n#pth-mockapi-wrap *, #pth-mockapi-wrap *::before, #pth-mockapi-wrap *::after { box-sizing: border-box; min-width: 0; }\r\n#pth-mockapi-wrap { font-family: 'Inter', system-ui, -apple-system, \"Segoe UI\", sans-serif; color: #1e293b; font-weight: 500; line-height: 1.6; max-width: 1480px; margin: 0 auto; padding: 0 20px; }\r\n#pth-mockapi-wrap .pth-ma-topbar { display: flex; align-items: center; justify-content: space-between; gap: 14px; flex-wrap: wrap; background: #fff; border: 1px solid #e2e8f0; border-radius: 14px; padding: 16px 22px; margin-bottom: 16px; }\r\n#pth-mockapi-wrap .pth-ma-brand { display: flex; align-items: center; gap: 14px; }\r\n#pth-mockapi-wrap .pth-ma-icon { width: 46px; height: 46px; border-radius: 11px; background: #7c3aed; color: #fff; display: flex; align-items: center; justify-content: center; font-size: 22px; flex: 0 0 auto; }\r\n#pth-mockapi-wrap .pth-ma-title { font-size: 1.4rem; font-weight: 800; color: #0f172a; margin: 0; }\r\n#pth-mockapi-wrap .pth-ma-sub { font-size: 0.8rem; color: #334155; font-weight: 600; margin: 2px 0 0; }\r\n#pth-mockapi-wrap .pth-ma-badges { display: flex; gap: 8px; flex-wrap: wrap; align-items: center; }\r\n#pth-mockapi-wrap .pth-ma-badge { font-size: 0.72rem; font-weight: 800; padding: 5px 11px; border-radius: 20px; border: 1px solid #e2e8f0; color: #334155; background: #f8fafc; }\r\n#pth-mockapi-wrap .pth-ma-badge.off { color: #047857; border-color: #a7f3d0; background: #ecfdf5; }\r\n#pth-mockapi-wrap .pth-ma-badge.ver { color: #6d28d9; border-color: #ddd6fe; background: #f5f3ff; }\r\n#pth-mockapi-wrap .pth-ma-badge.srv { color: #92400e; border-color: #fde68a; background: #fffbeb; }\r\n#pth-mockapi-wrap .pth-ma-badge.srv.on { color: #047857; border-color: #a7f3d0; background: #ecfdf5; }\r\n\r\n#pth-mockapi-wrap .pth-ma-tabs { display: flex; gap: 6px; background: #f1f5f9; border: 1px solid #e2e8f0; border-radius: 12px; padding: 5px; margin-bottom: 16px; flex-wrap: wrap; }\r\n#pth-mockapi-wrap .pth-ma-tab { flex: 1 1 auto; min-width: 110px; padding: 11px 12px; border: none; cursor: pointer; border-radius: 8px; background: transparent; color: #334155; font-family: inherit; font-size: 0.88rem; font-weight: 700; }\r\n#pth-mockapi-wrap .pth-ma-tab:hover { color: #0f172a; }\r\n#pth-mockapi-wrap .pth-ma-tab.active { background: #7c3aed; color: #fff; }\r\n#pth-mockapi-wrap .pth-ma-panel { display: none; }\r\n#pth-mockapi-wrap .pth-ma-panel.active { display: block; }\r\n\r\n#pth-mockapi-wrap .pth-ma-card { background: #fff; border: 1px solid #e2e8f0; border-radius: 14px; padding: 18px; margin-bottom: 16px; }\r\n#pth-mockapi-wrap .pth-ma-ctitle { font-size: 0.72rem; font-weight: 800; text-transform: uppercase; letter-spacing: 1px; color: #6d28d9; margin: 0 0 12px; }\r\n#pth-mockapi-wrap .pth-ma-io { display: grid; grid-template-columns: 340px 1fr; gap: 16px; align-items: start; }\r\n#pth-mockapi-wrap.full .pth-ma-io { grid-template-columns: 1fr; }\r\n\r\n#pth-mockapi-wrap .pth-ma-routes { display: flex; flex-direction: column; gap: 6px; max-height: 460px; overflow: auto; }\r\n#pth-mockapi-wrap .pth-ma-route { display: flex; align-items: center; gap: 8px; border: 1px solid #e2e8f0; border-radius: 8px; padding: 8px 10px; cursor: pointer; background: #fff; }\r\n#pth-mockapi-wrap .pth-ma-route.sel { border-color: #7c3aed; background: #f5f3ff; }\r\n#pth-mockapi-wrap .pth-ma-route.off { opacity: 0.5; }\r\n#pth-mockapi-wrap .pth-ma-m { font-size: 0.66rem; font-weight: 800; padding: 2px 6px; border-radius: 4px; color: #fff; flex: 0 0 auto; }\r\n#pth-mockapi-wrap .pth-ma-m.GET { background: #059669; } #pth-mockapi-wrap .pth-ma-m.POST { background: #2563eb; }\r\n#pth-mockapi-wrap .pth-ma-m.PUT { background: #d97706; } #pth-mockapi-wrap .pth-ma-m.DELETE { background: #dc2626; }\r\n#pth-mockapi-wrap .pth-ma-m.PATCH { background: #7c3aed; }\r\n#pth-mockapi-wrap .pth-ma-route .pp { font-family: 'JetBrains Mono', ui-monospace, monospace; font-size: 0.78rem; color: #0f172a; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; flex: 1; }\r\n\r\n#pth-mockapi-wrap .pth-ma-fg { display: flex; flex-direction: column; gap: 5px; margin-bottom: 12px; }\r\n#pth-mockapi-wrap .pth-ma-label { font-size: 0.7rem; font-weight: 800; text-transform: uppercase; letter-spacing: 0.5px; color: #334155; }\r\n#pth-mockapi-wrap .pth-ma-input, #pth-mockapi-wrap .pth-ma-select { width: 100%; border: 1px solid #e2e8f0; border-radius: 8px; padding: 8px 11px; background: #f8fafc; font-family: inherit; font-size: 0.86rem; font-weight: 600; color: #1e293b; outline: none; }\r\n#pth-mockapi-wrap .pth-ma-input:focus, #pth-mockapi-wrap .pth-ma-select:focus { border-color: #7c3aed; background: #fff; }\r\n#pth-mockapi-wrap .pth-ma-row { display: grid; grid-template-columns: 120px 1fr; gap: 8px; }\r\n#pth-mockapi-wrap .pth-ma-row3 { display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 8px; }\r\n#pth-mockapi-wrap textarea.pth-ma-code { width: 100%; min-height: 120px; resize: vertical; border: 1.5px solid #e2e8f0; border-radius: 10px; padding: 10px 12px; font-family: 'JetBrains Mono', ui-monospace, Menlo, monospace; font-size: 0.8rem; line-height: 1.6; color: #e2e8f0; background: #0f172a; outline: none; white-space: pre; overflow: auto; }\r\n#pth-mockapi-wrap textarea.pth-ma-code:focus { border-color: #7c3aed; }\r\n#pth-mockapi-wrap textarea.pth-ma-out { color: #e2e8f0; background: #0f172a; min-height: 180px; }\r\n\r\n#pth-mockapi-wrap .pth-ma-btn { display: inline-flex; align-items: center; gap: 7px; padding: 9px 15px; border-radius: 8px; border: 1px solid transparent; font-family: inherit; font-size: 0.85rem; font-weight: 700; cursor: pointer; }\r\n#pth-mockapi-wrap .pth-ma-btn.sm { padding: 7px 12px; font-size: 0.8rem; }\r\n#pth-mockapi-wrap .pth-ma-primary { background: #7c3aed; color: #fff; } #pth-mockapi-wrap .pth-ma-primary:hover { background: #6d28d9; }\r\n#pth-mockapi-wrap .pth-ma-green { background: #059669; color: #fff; } #pth-mockapi-wrap .pth-ma-green:hover { filter: brightness(0.94); }\r\n#pth-mockapi-wrap .pth-ma-ghost { background: #f8fafc; color: #334155; border-color: #e2e8f0; } #pth-mockapi-wrap .pth-ma-ghost:hover { background: #f1f5f9; color: #0f172a; }\r\n#pth-mockapi-wrap .pth-ma-danger { background: #fef2f2; color: #991b1b; border-color: #fecaca; } #pth-mockapi-wrap .pth-ma-danger:hover { background: #fee2e2; }\r\n#pth-mockapi-wrap .pth-ma-actions { display: flex; gap: 8px; flex-wrap: wrap; margin-top: 12px; }\r\n\r\n#pth-mockapi-wrap .pth-ma-chips { display: flex; gap: 6px; flex-wrap: wrap; margin: 6px 0 12px; }\r\n#pth-mockapi-wrap .pth-ma-chip { font-size: 0.76rem; font-weight: 700; padding: 5px 11px; border-radius: 20px; cursor: pointer; border: 1px solid #e2e8f0; background: #f8fafc; color: #475569; font-family: inherit; }\r\n#pth-mockapi-wrap .pth-ma-chip.on { background: #f5f3ff; border-color: #ddd6fe; color: #6d28d9; }\r\n#pth-mockapi-wrap .pth-ma-hint { font-size: 0.76rem; color: #64748b; margin-top: 6px; }\r\n#pth-mockapi-wrap .pth-ma-hint code { background: #f1f5f9; padding: 1px 5px; border-radius: 4px; color: #6d28d9; font-size: 0.72rem; }\r\n\r\n#pth-mockapi-wrap table.pth-ma-log { width: 100%; border-collapse: collapse; font-size: 0.8rem; }\r\n#pth-mockapi-wrap table.pth-ma-log th, #pth-mockapi-wrap table.pth-ma-log td { border-bottom: 1px solid #e2e8f0; padding: 7px 8px; text-align: left; }\r\n#pth-mockapi-wrap table.pth-ma-log th { color: #64748b; font-weight: 800; font-size: 0.7rem; text-transform: uppercase; }\r\n#pth-mockapi-wrap table.pth-ma-log td .st { font-weight: 800; } #pth-mockapi-wrap .st2 { color: #059669; } #pth-mockapi-wrap .st4, #pth-mockapi-wrap .st5 { color: #dc2626; }\r\n\r\n#pth-mockapi-wrap .pth-ma-prev { border: 1px solid #e2e8f0; border-radius: 10px; background: #fff; padding: 16px; min-height: 120px; }\r\n#pth-mockapi-wrap .pth-ma-note { font-size: 0.82rem; color: #334155; background: #f5f3ff; border: 1px solid #ddd6fe; border-radius: 8px; padding: 10px 12px; margin-top: 14px; }\r\n#pth-mockapi-wrap .pth-ma-note a { color: #6d28d9; font-weight: 700; }\r\n#pth-mockapi-wrap .pth-ma-msg { font-size: 0.82rem; border-radius: 8px; padding: 9px 12px; margin-top: 10px; display: none; }\r\n#pth-mockapi-wrap .pth-ma-msg.ok { display: block; color: #047857; background: #ecfdf5; border: 1px solid #a7f3d0; }\r\n#pth-mockapi-wrap .pth-ma-msg.bad { display: block; color: #991b1b; background: #fef2f2; border: 1px solid #fecaca; }\r\n\r\n#pth-mockapi-wrap .pth-ma-toast { position: fixed; bottom: 22px; right: 22px; z-index: 999999; display: none; padding: 13px 18px; border-radius: 10px; color: #fff; font-family: 'Inter', system-ui, sans-serif; font-size: 0.9rem; font-weight: 700; box-shadow: 0 8px 24px rgba(15,23,42,0.2); max-width: 320px; }\r\n#pth-mockapi-wrap .pth-ma-toast.show { display: block; }\r\n\r\n@media (max-width: 768px) { #pth-mockapi-wrap { padding: 0 10px; } #pth-mockapi-wrap .pth-ma-io { grid-template-columns: 1fr; } #pth-mockapi-wrap .pth-ma-row3 { grid-template-columns: 1fr; } }\r\n<\/style>\r\n\r\n<div id=\"pth-mockapi-wrap\">\r\n\r\n  <div class=\"pth-ma-topbar\">\r\n    <div class=\"pth-ma-brand\">\r\n      <div class=\"pth-ma-icon\" aria-hidden=\"true\">&#128268;<\/div>\r\n      <div>\r\n        <h2 class=\"pth-ma-title\">Mock API Studio<\/h2>\r\n        <p class=\"pth-ma-sub\">Mock REST &amp; GraphQL endpoints, stateful responses &amp; OpenAPI import &mdash; fully offline<\/p>\r\n      <\/div>\r\n    <\/div>\r\n    <div class=\"pth-ma-badges\">\r\n      <span class=\"pth-ma-badge ver\">v6.0<\/span>\r\n      <span class=\"pth-ma-badge off\">&#128274; 100% Offline<\/span>\r\n      <span class=\"pth-ma-badge srv\" id=\"ma-server-badge\" title=\"Toggle fetch interception\">&#9209;&#65039; Server off<\/span>\r\n      <button type=\"button\" class=\"pth-ma-btn pth-ma-ghost sm\" data-action=\"sample\">&#128161; Sample<\/button>\r\n      <button type=\"button\" class=\"pth-ma-btn pth-ma-ghost sm\" data-action=\"wide\">&#9906; Wide<\/button>\r\n      <button type=\"button\" class=\"pth-ma-btn pth-ma-danger sm\" data-action=\"reset\">&#128465;&#65039; Reset<\/button>\r\n    <\/div>\r\n  <\/div>\r\n\r\n  <div class=\"pth-ma-tabs\" role=\"tablist\">\r\n    <button type=\"button\" class=\"pth-ma-tab active\" data-tab=\"mock\" role=\"tab\">&#128225; REST Routes<\/button>\r\n    <button type=\"button\" class=\"pth-ma-tab\" data-tab=\"gql\" role=\"tab\">&#9883;&#65039; GraphQL<\/button>\r\n    <button type=\"button\" class=\"pth-ma-tab\" data-tab=\"env\" role=\"tab\">&#127760; Environment<\/button>\r\n    <button type=\"button\" class=\"pth-ma-tab\" data-tab=\"insp\" role=\"tab\">&#128203; Inspector<\/button>\r\n    <button type=\"button\" class=\"pth-ma-tab\" data-tab=\"tpl\" role=\"tab\">&#129513; Template<\/button>\r\n    <button type=\"button\" class=\"pth-ma-tab\" data-tab=\"io\" role=\"tab\">&#128260; Import \/ Export<\/button>\r\n  <\/div>\r\n\r\n  <!-- ===== REST ROUTES ===== -->\r\n  <div class=\"pth-ma-panel active\" data-panel=\"mock\">\r\n    <div class=\"pth-ma-io\">\r\n      <div class=\"pth-ma-card\">\r\n        <p class=\"pth-ma-ctitle\">Routes<\/p>\r\n        <div class=\"pth-ma-routes\" id=\"ma-route-list\"><\/div>\r\n        <div class=\"pth-ma-actions\"><button type=\"button\" class=\"pth-ma-btn pth-ma-primary sm\" data-action=\"new-route\">&#43; New Route<\/button><\/div>\r\n      <\/div>\r\n      <div class=\"pth-ma-card\">\r\n        <p class=\"pth-ma-ctitle\">Route editor<\/p>\r\n        <div class=\"pth-ma-row\">\r\n          <div class=\"pth-ma-fg\"><label class=\"pth-ma-label\" for=\"ma-method\">Method<\/label>\r\n            <select id=\"ma-method\" class=\"pth-ma-select\"><option>GET<\/option><option>POST<\/option><option>PUT<\/option><option>PATCH<\/option><option>DELETE<\/option><\/select><\/div>\r\n          <div class=\"pth-ma-fg\"><label class=\"pth-ma-label\" for=\"ma-path\">Path (use :param)<\/label><input id=\"ma-path\" class=\"pth-ma-input\" type=\"text\" placeholder=\"\/api\/users\/:id\"><\/div>\r\n        <\/div>\r\n        <div class=\"pth-ma-row3\">\r\n          <div class=\"pth-ma-fg\"><label class=\"pth-ma-label\" for=\"ma-status\">Status<\/label><input id=\"ma-status\" class=\"pth-ma-input\" type=\"number\" value=\"200\"><\/div>\r\n          <div class=\"pth-ma-fg\"><label class=\"pth-ma-label\" for=\"ma-latency\">Latency<\/label>\r\n            <select id=\"ma-latency\" class=\"pth-ma-select\"><option value=\"0\">Instant<\/option><option value=\"300\">Fast (300ms)<\/option><option value=\"800\">Slow 3G (800ms)<\/option><option value=\"2500\">Very slow (2.5s)<\/option><option value=\"flaky\">Flaky (random)<\/option><option value=\"timeout\">Timeout (error)<\/option><\/select><\/div>\r\n          <div class=\"pth-ma-fg\"><label class=\"pth-ma-label\" for=\"ma-mode\">Response mode<\/label>\r\n            <select id=\"ma-mode\" class=\"pth-ma-select\"><option value=\"static\">Static<\/option><option value=\"sequence\">Sequential<\/option><option value=\"state\">Stateful (CRUD)<\/option><\/select><\/div>\r\n        <\/div>\r\n        <div class=\"pth-ma-fg\"><label class=\"pth-ma-label\" for=\"ma-headers\">Headers (JSON)<\/label><textarea id=\"ma-headers\" class=\"pth-ma-code\" style=\"min-height:60px;\">{ \"Content-Type\": \"application\/json\" }<\/textarea><\/div>\r\n        <div class=\"pth-ma-fg\"><label class=\"pth-ma-label\" for=\"ma-body\" id=\"ma-body-label\">Response body (JSON)<\/label><textarea id=\"ma-body\" class=\"pth-ma-code\" placeholder='{ \"id\": \"{{id}}\", \"created\": \"{{$timestamp}}\" }'><\/textarea><\/div>\r\n        <p class=\"pth-ma-hint\">Tokens: <code>{{id}}<\/code> path params, <code>{{$uuid}}<\/code>, <code>{{$timestamp}}<\/code>, <code>{{$randomInt}}<\/code>, <code>{{$incrementId}}<\/code>, and <code>{{baseUrl}}<\/code> environment vars. Sequential = one body per line-separated JSON block (split on a line with only <code>---<\/code>).<\/p>\r\n        <div class=\"pth-ma-actions\">\r\n          <button type=\"button\" class=\"pth-ma-btn pth-ma-green sm\" data-action=\"save-route\">&#128190; Save Route<\/button>\r\n          <button type=\"button\" class=\"pth-ma-btn pth-ma-ghost sm\" data-action=\"test-route\">&#9654; Send Test Request<\/button>\r\n          <button type=\"button\" class=\"pth-ma-btn pth-ma-danger sm\" data-action=\"del-route\">&#128465;&#65039; Delete<\/button>\r\n        <\/div>\r\n        <div class=\"pth-ma-msg\" id=\"ma-test-msg\"><\/div>\r\n      <\/div>\r\n    <\/div>\r\n    <div class=\"pth-ma-note\">Need to generate large fake datasets, diff two payloads, query JSON, or write code with AI? Use <a href=\"https:\/\/schoolict.net\/tools\/lorem-ipsum-and-json-dummy-data-generator-pro\/\" target=\"_blank\" rel=\"noopener\">Dummy Data Generator<\/a>, <a href=\"https:\/\/schoolict.net\/tools\/text-diff-checker-comparator\/\" target=\"_blank\" rel=\"noopener\">Text Diff<\/a>, <a href=\"https:\/\/schoolict.net\/tools\/free-jsonpath-evaluator-extractor\/\" target=\"_blank\" rel=\"noopener\">JSON Studio<\/a>, or <a href=\"https:\/\/schoolict.net\/tools\/ai-code-studio\/\" target=\"_blank\" rel=\"noopener\">AI Code Studio<\/a>.<\/div>\r\n  <\/div>\r\n\r\n  <!-- ===== GRAPHQL ===== -->\r\n  <div class=\"pth-ma-panel\" data-panel=\"gql\">\r\n    <div class=\"pth-ma-card\">\r\n      <p class=\"pth-ma-ctitle\">GraphQL mock endpoint<\/p>\r\n      <div class=\"pth-ma-fg\"><label class=\"pth-ma-label\" for=\"ma-gql-path\">Endpoint path<\/label><input id=\"ma-gql-path\" class=\"pth-ma-input\" type=\"text\" value=\"\/graphql\"><\/div>\r\n      <p class=\"pth-ma-hint\">A single POST endpoint. Requests are matched by the operation\/field name in the query, and the matching response below is returned wrapped in <code>{ \"data\": ... }<\/code>.<\/p>\r\n      <div class=\"pth-ma-fg\" style=\"margin-top:12px;\"><label class=\"pth-ma-label\" for=\"ma-gql-op\">Operation name<\/label><input id=\"ma-gql-op\" class=\"pth-ma-input\" type=\"text\" placeholder=\"getUser\"><\/div>\r\n      <div class=\"pth-ma-fg\"><label class=\"pth-ma-label\" for=\"ma-gql-res\">Response data (JSON)<\/label><textarea id=\"ma-gql-res\" class=\"pth-ma-code\" placeholder='{ \"user\": { \"id\": 1, \"name\": \"Ada\" } }'><\/textarea><\/div>\r\n      <div class=\"pth-ma-actions\"><button type=\"button\" class=\"pth-ma-btn pth-ma-green sm\" data-action=\"save-gql\">&#128190; Save Operation<\/button><\/div>\r\n      <div id=\"ma-gql-list\" style=\"margin-top:14px;\"><\/div>\r\n    <\/div>\r\n  <\/div>\r\n\r\n  <!-- ===== ENVIRONMENT ===== -->\r\n  <div class=\"pth-ma-panel\" data-panel=\"env\">\r\n    <div class=\"pth-ma-card\">\r\n      <p class=\"pth-ma-ctitle\">Environment variables<\/p>\r\n      <p class=\"pth-ma-hint\" style=\"margin-bottom:12px;\">Use these anywhere in a path, header, or response as <code>{{name}}<\/code>. Handy for a shared base URL or token across many routes.<\/p>\r\n      <div id=\"ma-env-rows\"><\/div>\r\n      <div class=\"pth-ma-actions\"><button type=\"button\" class=\"pth-ma-btn pth-ma-ghost sm\" data-action=\"env-add\">&#43; Add variable<\/button><button type=\"button\" class=\"pth-ma-btn pth-ma-green sm\" data-action=\"env-save\">&#128190; Save<\/button><\/div>\r\n    <\/div>\r\n  <\/div>\r\n\r\n  <!-- ===== INSPECTOR ===== -->\r\n  <div class=\"pth-ma-panel\" data-panel=\"insp\">\r\n    <div class=\"pth-ma-card\">\r\n      <div style=\"display:flex;justify-content:space-between;align-items:center;margin-bottom:10px;flex-wrap:wrap;gap:8px;\">\r\n        <p class=\"pth-ma-ctitle\" style=\"margin:0;\">Request log<\/p>\r\n        <div style=\"display:flex;gap:8px;\"><button type=\"button\" class=\"pth-ma-btn pth-ma-ghost sm\" data-action=\"replay\">&#128260; Replay Last<\/button><button type=\"button\" class=\"pth-ma-btn pth-ma-danger sm\" data-action=\"clear-log\">&#128465;&#65039; Clear<\/button><\/div>\r\n      <\/div>\r\n      <table class=\"pth-ma-log\"><thead><tr><th>Time<\/th><th>Method<\/th><th>Path<\/th><th>Status<\/th><th>ms<\/th><th>Source<\/th><\/tr><\/thead><tbody id=\"ma-log-body\"><\/tbody><\/table>\r\n      <p class=\"pth-ma-hint\" id=\"ma-log-empty\" style=\"margin-top:10px;\">Turn the server on and send a request (or press Send Test Request) to see traffic here.<\/p>\r\n    <\/div>\r\n  <\/div>\r\n\r\n  <!-- ===== TEMPLATE ===== -->\r\n  <div class=\"pth-ma-panel\" data-panel=\"tpl\">\r\n    <div class=\"pth-ma-io\">\r\n      <div class=\"pth-ma-card\">\r\n        <p class=\"pth-ma-ctitle\">Bind a response to a template<\/p>\r\n        <div class=\"pth-ma-fg\"><label class=\"pth-ma-label\" for=\"ma-tpl-data\">Data (JSON)<\/label><textarea id=\"ma-tpl-data\" class=\"pth-ma-code\" placeholder='{ \"title\": \"Users\", \"list\": [ { \"name\": \"Ada\" }, { \"name\": \"Alan\" } ] }'><\/textarea><\/div>\r\n        <div class=\"pth-ma-fg\"><label class=\"pth-ma-label\" for=\"ma-tpl-html\">Template (HTML + tokens)<\/label><textarea id=\"ma-tpl-html\" class=\"pth-ma-code\" placeholder='<h3>{{title}}<\/h3><ul>{{#each list}}<li>{{name}}<\/li>{{\/each}}<\/ul>'><\/textarea><\/div>\r\n        <p class=\"pth-ma-hint\">Supports <code>{{field}}<\/code>, nested <code>{{a.b}}<\/code>, and <code>{{#each list}}...{{\/each}}<\/code>. Output is sanitised before preview.<\/p>\r\n        <div class=\"pth-ma-actions\"><button type=\"button\" class=\"pth-ma-btn pth-ma-primary sm\" data-action=\"render-tpl\">&#9654; Render<\/button><\/div>\r\n      <\/div>\r\n      <div class=\"pth-ma-card\">\r\n        <p class=\"pth-ma-ctitle\">Preview<\/p>\r\n        <div id=\"ma-tpl-prev\" class=\"pth-ma-prev\"><\/div>\r\n      <\/div>\r\n    <\/div>\r\n  <\/div>\r\n\r\n  <!-- ===== IMPORT \/ EXPORT ===== -->\r\n  <div class=\"pth-ma-panel\" data-panel=\"io\">\r\n    <div class=\"pth-ma-io\">\r\n      <div class=\"pth-ma-card\">\r\n        <p class=\"pth-ma-ctitle\">Import OpenAPI \/ Swagger<\/p>\r\n        <p class=\"pth-ma-hint\" style=\"margin-bottom:10px;\">Paste an OpenAPI 3.0 or Swagger 2.0 JSON spec. Each path + method becomes a mock route with an example response.<\/p>\r\n        <textarea id=\"ma-import\" class=\"pth-ma-code\" placeholder='{ \"openapi\": \"3.0.0\", \"paths\": { \"\/users\": { \"get\": {} } } }'><\/textarea>\r\n        <div class=\"pth-ma-actions\"><button type=\"button\" class=\"pth-ma-btn pth-ma-primary sm\" data-action=\"import-spec\">&#128229; Import Routes<\/button><\/div>\r\n        <div class=\"pth-ma-msg\" id=\"ma-import-msg\"><\/div>\r\n      <\/div>\r\n      <div class=\"pth-ma-card\">\r\n        <p class=\"pth-ma-ctitle\">Export<\/p>\r\n        <div class=\"pth-ma-chips\">\r\n          <button type=\"button\" class=\"pth-ma-chip on\" data-exp=\"openapi\">OpenAPI 3.0<\/button>\r\n          <button type=\"button\" class=\"pth-ma-chip\" data-exp=\"postman\">Postman<\/button>\r\n          <button type=\"button\" class=\"pth-ma-chip\" data-exp=\"fetch\">fetch()<\/button>\r\n          <button type=\"button\" class=\"pth-ma-chip\" data-exp=\"axios\">axios<\/button>\r\n          <button type=\"button\" class=\"pth-ma-chip\" data-exp=\"curl\">cURL<\/button>\r\n          <button type=\"button\" class=\"pth-ma-chip\" data-exp=\"share\">Share link<\/button>\r\n        <\/div>\r\n        <textarea id=\"ma-export-out\" class=\"pth-ma-code pth-ma-out\" readonly><\/textarea>\r\n        <div class=\"pth-ma-actions\"><button type=\"button\" class=\"pth-ma-btn pth-ma-ghost sm\" data-action=\"copy\" data-src=\"ma-export-out\">&#128203; Copy<\/button><button type=\"button\" class=\"pth-ma-btn pth-ma-primary sm\" data-action=\"download-export\">&#8681; Download<\/button><\/div>\r\n      <\/div>\r\n    <\/div>\r\n  <\/div>\r\n\r\n  <div id=\"pth-ma-toast\" class=\"pth-ma-toast\" role=\"status\" aria-live=\"polite\"><\/div>\r\n<\/div>\r\n\r\n<script data-no-optimize=\"1\" data-no-minify=\"1\" data-cfasync=\"false\">\r\n(function () {\r\n  'use strict';\r\n  if (window.pthMockApiApp) { return; }\r\n  var STORE = 'pthMockApiV6';\r\n\r\n  var App = {\r\n    initialized: false, root: null, S: null, sel: -1, exp: 'openapi', serverOn: false, _inc: {}, _seq: {}, _state: {}, _origFetch: null, _lastReq: null,\r\n\r\n    init: function () {\r\n      this.root = document.getElementById('pth-mockapi-wrap');\r\n      if (!this.root) { return; }\r\n      this.load();\r\n      this.attach();\r\n      this.renderRoutes(); this.renderGql(); this.renderEnv();\r\n    },\r\n\r\n    fresh: function () { return { routes: [], gql: { path: '\/graphql', ops: [] }, vars: { baseUrl: 'https:\/\/api.example.com', token: 'demo-token' }, log: [] }; },\r\n    load: function () { try { var v = localStorage.getItem(STORE); this.S = v ? JSON.parse(v) : this.fresh(); if (!this.S.routes) { this.S = this.fresh(); } if (!this.S.gql) { this.S.gql = { path: '\/graphql', ops: [] }; } if (!this.S.vars) { this.S.vars = {}; } } catch (e) { this.S = this.fresh(); } },\r\n    save: function () { try { localStorage.setItem(STORE, JSON.stringify(this.S)); } catch (e) {} },\r\n\r\n    attach: function () {\r\n      var self = this;\r\n      this.root.addEventListener('click', function (e) {\r\n        var el = e.target;\r\n        while (el && el !== self.root && !el.getAttribute('data-action') && !el.getAttribute('data-tab') && !el.getAttribute('data-exp') && !el.getAttribute('data-route') && !el.getAttribute('data-delvar') && !el.getAttribute('data-delop')) { el = el.parentNode; }\r\n        if (!el || el === self.root) { return; }\r\n        if (el.getAttribute('data-tab')) { self.switchTab(el.getAttribute('data-tab')); return; }\r\n        if (el.getAttribute('data-exp')) { self.setExp(el.getAttribute('data-exp')); return; }\r\n        if (el.getAttribute('data-route') !== null && el.getAttribute('data-route') !== undefined && el.getAttribute('data-route') !== '') { self.selectRoute(parseInt(el.getAttribute('data-route'), 10)); return; }\r\n        if (el.getAttribute('data-delvar')) { self.delVar(el.getAttribute('data-delvar')); return; }\r\n        if (el.getAttribute('data-delop') !== null && el.getAttribute('data-delop') !== undefined && el.getAttribute('data-delop') !== '') { self.delOp(parseInt(el.getAttribute('data-delop'), 10)); return; }\r\n        var a = el.getAttribute('data-action');\r\n        if (a) { try { self.handle(a, el); } catch (err) { self.toast('Something went wrong. Check your input.', 'error'); if (window.console) { console.error('[MockApi]', err); } } }\r\n      });\r\n      var badge = document.getElementById('ma-server-badge');\r\n      if (badge) { badge.style.cursor = 'pointer'; badge.addEventListener('click', function () { self.toggleServer(); }); }\r\n    },\r\n\r\n    handle: function (a, el) {\r\n      switch (a) {\r\n        case 'sample': this.loadSample(); break;\r\n        case 'wide': this.root.className = this.root.className.indexOf('full') > -1 ? '' : 'full'; break;\r\n        case 'reset': this.reset(); break;\r\n        case 'new-route': this.newRoute(); break;\r\n        case 'save-route': this.saveRoute(); break;\r\n        case 'del-route': this.delRoute(); break;\r\n        case 'test-route': this.testRoute(); break;\r\n        case 'save-gql': this.saveGql(); break;\r\n        case 'env-add': this.envAdd(); break;\r\n        case 'env-save': this.envSave(); break;\r\n        case 'replay': this.replay(); break;\r\n        case 'clear-log': this.S.log = []; this.save(); this.renderLog(); break;\r\n        case 'render-tpl': this.renderTpl(); break;\r\n        case 'import-spec': this.importSpec(); break;\r\n        case 'download-export': this.downloadExport(); break;\r\n        case 'copy': this.copy(el.getAttribute('data-src')); break;\r\n      }\r\n    },\r\n\r\n    switchTab: function (name) {\r\n      var tabs = this.root.querySelectorAll('.pth-ma-tab'), i;\r\n      for (i = 0; i < tabs.length; i++) { tabs[i].className = 'pth-ma-tab' + (tabs[i].getAttribute('data-tab') === name ? ' active' : ''); }\r\n      var panels = this.root.querySelectorAll('.pth-ma-panel');\r\n      for (i = 0; i < panels.length; i++) { panels[i].className = 'pth-ma-panel' + (panels[i].getAttribute('data-panel') === name ? ' active' : ''); }\r\n      if (name === 'insp') { this.renderLog(); }\r\n      if (name === 'io') { this.renderExport(); }\r\n    },\r\n\r\n    v: function (id) { var e = document.getElementById(id); return e ? e.value : ''; },\r\n    setV: function (id, val) { var e = document.getElementById(id); if (e) { e.value = val; } },\r\n\r\n    \/* ---------- routes ---------- *\/\r\n    newRoute: function () {\r\n      this.S.routes.push({ method: 'GET', path: '\/api\/new', status: 200, latency: '0', mode: 'static', headers: '{ \"Content-Type\": \"application\/json\" }', body: '{\\n  \"message\": \"hello\"\\n}', enabled: true });\r\n      this.sel = this.S.routes.length - 1; this.save(); this.renderRoutes(); this.loadRoute();\r\n    },\r\n\r\n    selectRoute: function (i) { this.sel = i; this.renderRoutes(); this.loadRoute(); },\r\n\r\n    loadRoute: function () {\r\n      var r = this.S.routes[this.sel]; if (!r) { return; }\r\n      this.setV('ma-method', r.method); this.setV('ma-path', r.path); this.setV('ma-status', r.status);\r\n      this.setV('ma-latency', r.latency); this.setV('ma-mode', r.mode); this.setV('ma-headers', r.headers); this.setV('ma-body', r.body);\r\n    },\r\n\r\n    saveRoute: function () {\r\n      if (this.sel < 0) { this.newRoute(); }\r\n      var r = this.S.routes[this.sel];\r\n      r.method = this.v('ma-method'); r.path = this.v('ma-path'); r.status = parseInt(this.v('ma-status'), 10) || 200;\r\n      r.latency = this.v('ma-latency'); r.mode = this.v('ma-mode'); r.headers = this.v('ma-headers'); r.body = this.v('ma-body');\r\n      this.save(); this.renderRoutes(); this.toast('Route saved.', 'success');\r\n    },\r\n\r\n    delRoute: function () { if (this.sel < 0) { return; } this.S.routes.splice(this.sel, 1); this.sel = -1; this.save(); this.renderRoutes(); this.toast('Route deleted.', 'info'); },\r\n\r\n    renderRoutes: function () {\r\n      var host = document.getElementById('ma-route-list'); if (!host) { return; }\r\n      host.innerHTML = '';\r\n      if (!this.S.routes.length) { var p = document.createElement('p'); p.className = 'pth-ma-hint'; p.textContent = 'No routes yet. Press New Route.'; host.appendChild(p); return; }\r\n      for (var i = 0; i < this.S.routes.length; i++) {\r\n        var r = this.S.routes[i];\r\n        var row = document.createElement('div'); row.className = 'pth-ma-route' + (i === this.sel ? ' sel' : '') + (r.enabled === false ? ' off' : '');\r\n        row.setAttribute('data-route', String(i));\r\n        var m = document.createElement('span'); m.className = 'pth-ma-m ' + r.method; m.textContent = r.method;\r\n        var pp = document.createElement('span'); pp.className = 'pp'; pp.textContent = r.path;\r\n        row.appendChild(m); row.appendChild(pp);\r\n        host.appendChild(row);\r\n      }\r\n    },\r\n\r\n    \/* ---------- token + var substitution ---------- *\/\r\n    subst: function (str, params) {\r\n      var self = this;\r\n      if (str === null || str === undefined) { return ''; }\r\n      str = String(str);\r\n      \/* env vars *\/\r\n      var vars = this.S.vars || {}, k;\r\n      for (k in vars) { if (vars.hasOwnProperty(k)) { str = str.replace(new RegExp('\\\\{\\\\{\\\\s*' + k + '\\\\s*\\\\}\\\\}', 'g'), vars[k]); } }\r\n      \/* path params *\/\r\n      if (params) { for (k in params) { if (params.hasOwnProperty(k)) { str = str.replace(new RegExp('\\\\{\\\\{\\\\s*' + k + '\\\\s*\\\\}\\\\}', 'g'), params[k]); } } }\r\n      \/* dynamic tokens *\/\r\n      str = str.replace(\/\\{\\{\\s*\\$uuid\\s*\\}\\}\/g, function () { return self.uuid(); });\r\n      str = str.replace(\/\\{\\{\\s*\\$timestamp\\s*\\}\\}\/g, function () { return new Date().toISOString(); });\r\n      str = str.replace(\/\\{\\{\\s*\\$randomInt\\s*\\}\\}\/g, function () { return String(Math.floor(Math.random() * 10000)); });\r\n      str = str.replace(\/\\{\\{\\s*\\$incrementId\\s*\\}\\}\/g, function () { self._gid = (self._gid || 0) + 1; return String(self._gid); });\r\n      return str;\r\n    },\r\n\r\n    uuid: function () { return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(\/[xy]\/g, function (c) { var r = Math.random() * 16 | 0, v = c === 'x' ? r : (r & 0x3 | 0x8); return v.toString(16); }); },\r\n\r\n    \/* ---------- matching ---------- *\/\r\n    pathToRegex: function (path) {\r\n      var names = [];\r\n      var pat = path.replace(\/[.+*?^${}()|[\\]\\\\]\/g, '\\\\$&').replace(\/\\\/:(\\w+)\/g, function (m, n) { names.push(n); return '\/([^\/]+)'; });\r\n      return { re: new RegExp('^' + pat + '$'), names: names };\r\n    },\r\n\r\n    matchRoute: function (method, pathname) {\r\n      for (var i = 0; i < this.S.routes.length; i++) {\r\n        var r = this.S.routes[i];\r\n        if (r.enabled === false) { continue; }\r\n        if (r.method !== method) { continue; }\r\n        var pr = this.pathToRegex(r.path.split('?')[0]);\r\n        var m = pr.re.exec(pathname);\r\n        if (m) { var params = {}; for (var j = 0; j < pr.names.length; j++) { params[pr.names[j]] = decodeURIComponent(m[j + 1]); } return { route: r, idx: i, params: params }; }\r\n      }\r\n      return null;\r\n    },\r\n\r\n    buildBody: function (match, reqBody) {\r\n      var r = match.route;\r\n      if (r.mode === 'sequence') {\r\n        var parts = String(r.body).split(\/\\n\\s*---\\s*\\n\/);\r\n        var key = r.method + ' ' + r.path;\r\n        this._seq[key] = (this._seq[key] || 0);\r\n        var body = parts[this._seq[key] % parts.length];\r\n        this._seq[key]++;\r\n        return this.subst(body, match.params);\r\n      }\r\n      if (r.mode === 'state') {\r\n        var sk = r.path.split('\/:')[0];\r\n        if (!this._state[sk]) { this._state[sk] = []; }\r\n        if (r.method === 'POST') { var obj = {}; try { obj = JSON.parse(this.subst(r.body, match.params)); } catch (e) {} this._state[sk].push(obj); return JSON.stringify(obj); }\r\n        if (r.method === 'DELETE') { this._state[sk] = []; return JSON.stringify({ deleted: true }); }\r\n        return JSON.stringify(this._state[sk]);\r\n      }\r\n      return this.subst(r.body, match.params);\r\n    },\r\n\r\n    \/* ---------- fetch interception ---------- *\/\r\n    toggleServer: function () {\r\n      if (this.serverOn) { this.stopServer(); } else { this.startServer(); }\r\n    },\r\n\r\n    startServer: function () {\r\n      var self = this;\r\n      if (this.serverOn) { return; }\r\n      this._origFetch = window.fetch ? window.fetch.bind(window) : null;\r\n      window.fetch = function (input, opts) {\r\n        opts = opts || {};\r\n        var url = typeof input === 'string' ? input : (input && input.url) || '';\r\n        var method = (opts.method || (input && input.method) || 'GET').toUpperCase();\r\n        var a = document.createElement('a'); a.href = url; var pathname = a.pathname; var t0 = Date.now();\r\n        \/* GraphQL *\/\r\n        if (self.S.gql && self.S.gql.ops.length && pathname === self.S.gql.path && method === 'POST') {\r\n          var opName = self.gqlOpFromBody(opts.body);\r\n          var op = self.findOp(opName);\r\n          if (op) { var gqlBody = '{\"data\":' + self.subst(op.response, {}) + '}'; self.logReq('POST', pathname, 200, Date.now() - t0, 'graphql'); return self.respond(gqlBody, 200, { 'Content-Type': 'application\/json' }, 0); }\r\n        }\r\n        var match = self.matchRoute(method, pathname);\r\n        if (!match) { if (self._origFetch) { return self._origFetch(input, opts); } return Promise.reject(new Error('No mock and no network')); }\r\n        var r = match.route;\r\n        var body = self.buildBody(match, opts.body);\r\n        var headers = {}; try { headers = JSON.parse(self.subst(r.headers, match.params)); } catch (e) { headers = { 'Content-Type': 'application\/json' }; }\r\n        var delay = self.latencyMs(r.latency);\r\n        self._lastReq = { input: input, opts: opts };\r\n        if (r.latency === 'timeout') { self.logReq(method, pathname, 0, delay, 'mock'); return new Promise(function (_, rej) { setTimeout(function () { rej(new Error('Mock timeout')); }, delay); }); }\r\n        self.logReq(method, pathname, r.status, delay, 'mock');\r\n        return self.respond(body, r.status, headers, delay);\r\n      };\r\n      this.serverOn = true; this.setBadge(true);\r\n      this.toast('Mock server on - fetch is intercepted.', 'success');\r\n    },\r\n\r\n    stopServer: function () { if (this._origFetch) { window.fetch = this._origFetch; } this.serverOn = false; this.setBadge(false); this.toast('Mock server off.', 'info'); },\r\n\r\n    setBadge: function (on) { var b = document.getElementById('ma-server-badge'); if (b) { b.className = 'pth-ma-badge srv' + (on ? ' on' : ''); b.innerHTML = on ? '\\u25B6\\uFE0F Server on' : '\\u23F9\\uFE0F Server off'; } },\r\n\r\n    latencyMs: function (l) { if (l === 'flaky') { return 200 + Math.floor(Math.random() * 2000); } if (l === 'timeout') { return 3000; } return parseInt(l, 10) || 0; },\r\n\r\n    respond: function (bodyText, status, headers, delay) {\r\n      return new Promise(function (resolve) {\r\n        setTimeout(function () {\r\n          var resp;\r\n          if (typeof Response === 'function') { resp = new Response(bodyText, { status: status, headers: headers }); }\r\n          else { resp = { status: status, ok: status >= 200 && status < 300, headers: headers, text: function () { return Promise.resolve(bodyText); }, json: function () { return Promise.resolve(JSON.parse(bodyText)); } }; }\r\n          resolve(resp);\r\n        }, delay);\r\n      });\r\n    },\r\n\r\n    gqlOpFromBody: function (body) {\r\n      try { var o = JSON.parse(body); var q = o.query || ''; var m = q.match(\/\\{\\s*(\\w+)\/); return m ? m[1] : ''; } catch (e) { return ''; }\r\n    },\r\n    findOp: function (name) { for (var i = 0; i < this.S.gql.ops.length; i++) { if (this.S.gql.ops[i].name === name) { return this.S.gql.ops[i]; } } return this.S.gql.ops[0] || null; },\r\n\r\n    \/* ---------- test \/ log ---------- *\/\r\n    testRoute: function () {\r\n      var r = this.S.routes[this.sel]; var msg = document.getElementById('ma-test-msg');\r\n      if (!r) { msg.className = 'pth-ma-msg bad'; msg.textContent = 'Select or save a route first.'; return; }\r\n      var wasOff = !this.serverOn; if (wasOff) { this.startServer(); }\r\n      var self = this; var testUrl = (this.S.vars.baseUrl || '') + r.path.replace(\/:(\\w+)\/g, '1').split('?')[0];\r\n      fetch(testUrl, { method: r.method }).then(function (res) {\r\n        return res.text().then(function (t) { msg.className = 'pth-ma-msg ok'; msg.textContent = 'HTTP ' + res.status + ' - ' + t.slice(0, 200); if (wasOff) { self.stopServer(); } });\r\n      })['catch'](function (err) { msg.className = 'pth-ma-msg bad'; msg.textContent = 'Request failed: ' + err.message; if (wasOff) { self.stopServer(); } });\r\n    },\r\n\r\n    logReq: function (method, path, status, ms, src) {\r\n      this.S.log.unshift({ t: new Date().toTimeString().slice(0, 8), method: method, path: path, status: status, ms: ms, src: src });\r\n      if (this.S.log.length > 50) { this.S.log.pop(); } this.save(); this.renderLog();\r\n    },\r\n\r\n    renderLog: function () {\r\n      var tb = document.getElementById('ma-log-body'), empty = document.getElementById('ma-log-empty'); if (!tb) { return; }\r\n      tb.innerHTML = '';\r\n      if (!this.S.log.length) { if (empty) { empty.style.display = 'block'; } return; }\r\n      if (empty) { empty.style.display = 'none'; }\r\n      for (var i = 0; i < this.S.log.length; i++) {\r\n        var l = this.S.log[i], tr = document.createElement('tr');\r\n        tr.appendChild(this.td(l.t)); tr.appendChild(this.td(l.method)); tr.appendChild(this.td(l.path));\r\n        var s = document.createElement('td'); var sp = document.createElement('span'); sp.className = 'st st' + String(l.status).charAt(0); sp.textContent = l.status || 'ERR'; s.appendChild(sp); tr.appendChild(s);\r\n        tr.appendChild(this.td(String(l.ms))); tr.appendChild(this.td(l.src));\r\n        tb.appendChild(tr);\r\n      }\r\n    },\r\n    td: function (t) { var d = document.createElement('td'); d.textContent = t; return d; },\r\n\r\n    replay: function () {\r\n      if (!this._lastReq) { this.toast('No request to replay yet.', 'error'); return; }\r\n      var wasOff = !this.serverOn; if (wasOff) { this.startServer(); }\r\n      var self = this;\r\n      fetch(this._lastReq.input, this._lastReq.opts)['catch'](function () {}).then(function () { if (wasOff) { self.stopServer(); } });\r\n      this.switchTab('insp');\r\n    },\r\n\r\n    \/* ---------- graphql ui ---------- *\/\r\n    saveGql: function () {\r\n      this.S.gql.path = this.v('ma-gql-path') || '\/graphql';\r\n      var name = this.v('ma-gql-op').replace(\/^\\s+|\\s+$\/g, ''); var res = this.v('ma-gql-res');\r\n      if (!name) { this.toast('Enter an operation name.', 'error'); return; }\r\n      var found = false; for (var i = 0; i < this.S.gql.ops.length; i++) { if (this.S.gql.ops[i].name === name) { this.S.gql.ops[i].response = res; found = true; } }\r\n      if (!found) { this.S.gql.ops.push({ name: name, response: res }); }\r\n      this.save(); this.renderGql(); this.setV('ma-gql-op', ''); this.setV('ma-gql-res', ''); this.toast('Operation saved.', 'success');\r\n    },\r\n    delOp: function (i) { this.S.gql.ops.splice(i, 1); this.save(); this.renderGql(); },\r\n    renderGql: function () {\r\n      var host = document.getElementById('ma-gql-list'); if (!host) { return; }\r\n      this.setV('ma-gql-path', this.S.gql.path); host.innerHTML = '';\r\n      for (var i = 0; i < this.S.gql.ops.length; i++) {\r\n        var row = document.createElement('div'); row.className = 'pth-ma-route';\r\n        var nm = document.createElement('span'); nm.className = 'pp'; nm.textContent = this.S.gql.ops[i].name;\r\n        var x = document.createElement('button'); x.type = 'button'; x.className = 'pth-ma-btn pth-ma-danger sm'; x.setAttribute('data-delop', String(i)); x.textContent = '\\u00D7';\r\n        row.appendChild(nm); row.appendChild(x); host.appendChild(row);\r\n      }\r\n    },\r\n\r\n    \/* ---------- env ---------- *\/\r\n    envAdd: function () { var name = window.prompt('Variable name:'); if (!name) { return; } this.S.vars[name] = ''; this.save(); this.renderEnv(); },\r\n    delVar: function (name) { delete this.S.vars[name]; this.save(); this.renderEnv(); },\r\n    envSave: function () {\r\n      var rows = this.root.querySelectorAll('#ma-env-rows [data-var]');\r\n      for (var i = 0; i < rows.length; i++) { this.S.vars[rows[i].getAttribute('data-var')] = rows[i].value; }\r\n      this.save(); this.toast('Environment saved.', 'success');\r\n    },\r\n    renderEnv: function () {\r\n      var host = document.getElementById('ma-env-rows'); if (!host) { return; }\r\n      host.innerHTML = ''; var k;\r\n      for (k in this.S.vars) { if (!this.S.vars.hasOwnProperty(k)) { continue; }\r\n        var row = document.createElement('div'); row.className = 'pth-ma-row'; row.style.marginBottom = '8px';\r\n        var lab = document.createElement('div'); lab.className = 'pth-ma-input'; lab.style.background = '#f1f5f9'; lab.style.fontWeight = '700'; lab.textContent = '{{' + k + '}}';\r\n        var wrap = document.createElement('div'); wrap.style.display = 'flex'; wrap.style.gap = '6px';\r\n        var inp = document.createElement('input'); inp.className = 'pth-ma-input'; inp.setAttribute('data-var', k); inp.value = this.S.vars[k]; inp.setAttribute('aria-label', k);\r\n        var x = document.createElement('button'); x.type = 'button'; x.className = 'pth-ma-btn pth-ma-danger sm'; x.setAttribute('data-delvar', k); x.textContent = '\\u00D7';\r\n        wrap.appendChild(inp); wrap.appendChild(x);\r\n        row.appendChild(lab); row.appendChild(wrap); host.appendChild(row);\r\n      }\r\n    },\r\n\r\n    \/* ---------- template ---------- *\/\r\n    renderTpl: function () {\r\n      var data = {}; try { data = JSON.parse(this.v('ma-tpl-data') || '{}'); } catch (e) { this.toast('Data is not valid JSON.', 'error'); return; }\r\n      var tpl = this.v('ma-tpl-html');\r\n      var out = this.mustache(tpl, data);\r\n      var prev = document.getElementById('ma-tpl-prev'); if (prev) { prev.innerHTML = this.sanitize(out); }\r\n    },\r\n    mustache: function (tpl, data) {\r\n      var self = this;\r\n      tpl = tpl.replace(\/\\{\\{#each\\s+([\\w.]+)\\s*\\}\\}([\\s\\S]*?)\\{\\{\\\/each\\}\\}\/g, function (m, key, inner) {\r\n        var arr = self.dig(data, key); if (!arr || !arr.length) { return ''; }\r\n        var out = ''; for (var i = 0; i < arr.length; i++) { out += self.mustache(inner, arr[i]); } return out;\r\n      });\r\n      tpl = tpl.replace(\/\\{\\{\\s*([\\w.]+)\\s*\\}\\}\/g, function (m, key) { var v = self.dig(data, key); return v === undefined || v === null ? '' : String(v); });\r\n      return tpl;\r\n    },\r\n    dig: function (o, path) { var parts = path.split('.'); var cur = o; for (var i = 0; i < parts.length; i++) { if (cur === null || cur === undefined) { return undefined; } cur = cur[parts[i]]; } return cur; },\r\n    sanitize: function (html) {\r\n      html = String(html).replace(\/<\\s*(script|style|iframe|object|embed|link|meta)[^>]*>[\\s\\S]*?<\\s*\\\/\\s*\\1\\s*>\/gi, '');\r\n      html = html.replace(\/<\\s*(script|style|iframe|object|embed|link|meta)[^>]*>\/gi, '');\r\n      html = html.replace(\/\\son\\w+\\s*=\\s*(\"[^\"]*\"|'[^']*'|[^\\s>]+)\/gi, '');\r\n      html = html.replace(\/(href|src)\\s*=\\s*(\"javascript:[^\"]*\"|'javascript:[^']*')\/gi, '$1=\"#\"');\r\n      return html;\r\n    },\r\n\r\n    \/* ---------- import ---------- *\/\r\n    importSpec: function () {\r\n      var msg = document.getElementById('ma-import-msg'); var spec;\r\n      try { spec = JSON.parse(this.v('ma-import')); } catch (e) { msg.className = 'pth-ma-msg bad'; msg.textContent = 'Not valid JSON.'; return; }\r\n      var paths = spec.paths || {}; var count = 0, p, method;\r\n      for (p in paths) { if (!paths.hasOwnProperty(p)) { continue; }\r\n        var routePath = p.replace(\/\\{(\\w+)\\}\/g, ':$1');\r\n        for (method in paths[p]) { if (!paths[p].hasOwnProperty(method)) { continue; }\r\n          if (['get', 'post', 'put', 'patch', 'delete'].indexOf(method.toLowerCase()) === -1) { continue; }\r\n          var ex = this.exampleFrom(paths[p][method]);\r\n          this.S.routes.push({ method: method.toUpperCase(), path: routePath, status: 200, latency: '0', mode: 'static', headers: '{ \"Content-Type\": \"application\/json\" }', body: ex, enabled: true });\r\n          count++;\r\n        }\r\n      }\r\n      this.save(); this.renderRoutes();\r\n      msg.className = 'pth-ma-msg ok'; msg.textContent = 'Imported ' + count + ' route(s).';\r\n      this.switchTab('mock');\r\n    },\r\n    exampleFrom: function (op) {\r\n      try {\r\n        var resp = op.responses && (op.responses['200'] || op.responses['201'] || op.responses.default);\r\n        if (resp && resp.content && resp.content['application\/json']) { var mt = resp.content['application\/json']; if (mt.example) { return JSON.stringify(mt.example, null, 2); } if (mt.schema && mt.schema.example) { return JSON.stringify(mt.schema.example, null, 2); } }\r\n      } catch (e) {}\r\n      return '{\\n  \"message\": \"mocked response\"\\n}';\r\n    },\r\n\r\n    \/* ---------- export ---------- *\/\r\n    setExp: function (f) { this.exp = f; var c = this.root.querySelectorAll('[data-exp]'), i; for (i = 0; i < c.length; i++) { c[i].className = 'pth-ma-chip' + (c[i].getAttribute('data-exp') === f ? ' on' : ''); } this.renderExport(); },\r\n    renderExport: function () {\r\n      var out = document.getElementById('ma-export-out'); if (!out) { return; }\r\n      if (this.exp === 'openapi') { out.value = this.expOpenApi(); }\r\n      else if (this.exp === 'postman') { out.value = this.expPostman(); }\r\n      else if (this.exp === 'share') { out.value = this.expShare(); }\r\n      else { out.value = this.expCode(this.exp); }\r\n    },\r\n    expOpenApi: function () {\r\n      var spec = { openapi: '3.0.0', info: { title: 'Mock API', version: '1.0.0' }, paths: {} };\r\n      for (var i = 0; i < this.S.routes.length; i++) { var r = this.S.routes[i]; var op = r.path.replace(\/:(\\w+)\/g, '{$1}').split('?')[0]; if (!spec.paths[op]) { spec.paths[op] = {}; } var ex; try { ex = JSON.parse(r.body); } catch (e) { ex = {}; } spec.paths[op][r.method.toLowerCase()] = { responses: { '200': { description: 'OK', content: { 'application\/json': { example: ex } } } } }; }\r\n      return JSON.stringify(spec, null, 2);\r\n    },\r\n    expPostman: function () {\r\n      var col = { info: { name: 'Mock API', schema: 'https:\/\/schema.getpostman.com\/json\/collection\/v2.1.0\/collection.json' }, item: [] };\r\n      for (var i = 0; i < this.S.routes.length; i++) { var r = this.S.routes[i]; col.item.push({ name: r.method + ' ' + r.path, request: { method: r.method, url: '{{baseUrl}}' + r.path.split('?')[0] } }); }\r\n      return JSON.stringify(col, null, 2);\r\n    },\r\n    expCode: function (kind) {\r\n      var r = this.S.routes[this.sel] || this.S.routes[0]; if (!r) { return '\/\/ Save a route first.'; }\r\n      var url = (this.S.vars.baseUrl || 'https:\/\/api.example.com') + r.path.replace(\/:(\\w+)\/g, '1').split('?')[0];\r\n      if (kind === 'curl') { return \"curl -X \" + r.method + \" '\" + url + \"'\"; }\r\n      if (kind === 'axios') { return \"import axios from 'axios';\\n\\nawait axios({\\n  method: '\" + r.method.toLowerCase() + \"',\\n  url: '\" + url + \"'\\n});\"; }\r\n      return \"const res = await fetch('\" + url + \"', {\\n  method: '\" + r.method + \"'\\n});\\nconst data = await res.json();\";\r\n    },\r\n    expShare: function () {\r\n      try { return location.protocol + '\/\/' + location.host + location.pathname + '#mock=' + btoa(unescape(encodeURIComponent(JSON.stringify(this.S)))); } catch (e) { return 'Could not build link.'; }\r\n    },\r\n    downloadExport: function () {\r\n      var out = document.getElementById('ma-export-out'); if (!out || !out.value) { this.renderExport(); }\r\n      var val = (document.getElementById('ma-export-out') || {}).value || ''; if (!val) { this.toast('Nothing to export.', 'error'); return; }\r\n      var ext = { openapi: 'json', postman: 'json', fetch: 'js', axios: 'js', curl: 'sh', share: 'txt' }[this.exp] || 'txt';\r\n      var blob = new Blob([val], { type: 'text\/plain' }), url = URL.createObjectURL(blob);\r\n      var a = document.createElement('a'); a.href = url; a.download = 'mock-api.' + ext; document.body.appendChild(a); a.click(); document.body.removeChild(a);\r\n      setTimeout(function () { URL.revokeObjectURL(url); }, 1500); this.toast('Downloaded.', 'success');\r\n    },\r\n\r\n    \/* ---------- shared ---------- *\/\r\n    copy: function (id) { var el = document.getElementById(id), self = this; if (!el || !el.value) { this.toast('Nothing to copy.', 'error'); return; } if (navigator.clipboard && navigator.clipboard.writeText) { navigator.clipboard.writeText(el.value).then(function () { self.toast('Copied.', 'success'); })['catch'](function () { self.toast('Copy blocked.', 'error'); }); } else { el.focus(); el.select(); } },\r\n\r\n    loadSample: function () {\r\n      this.S.routes = [\r\n        { method: 'GET', path: '\/api\/users', status: 200, latency: '300', mode: 'static', headers: '{ \"Content-Type\": \"application\/json\" }', body: '[\\n  { \"id\": 1, \"name\": \"Ada\" },\\n  { \"id\": 2, \"name\": \"Alan\" }\\n]', enabled: true },\r\n        { method: 'GET', path: '\/api\/users\/:id', status: 200, latency: '0', mode: 'static', headers: '{ \"Content-Type\": \"application\/json\" }', body: '{\\n  \"id\": \"{{id}}\",\\n  \"name\": \"User {{id}}\",\\n  \"requestId\": \"{{$uuid}}\"\\n}', enabled: true },\r\n        { method: 'POST', path: '\/api\/users', status: 201, latency: '0', mode: 'state', headers: '{ \"Content-Type\": \"application\/json\" }', body: '{\\n  \"id\": \"{{$incrementId}}\",\\n  \"created\": \"{{$timestamp}}\"\\n}', enabled: true }\r\n      ];\r\n      this.S.gql.ops = [{ name: 'getUser', response: '{ \"user\": { \"id\": 1, \"name\": \"Ada\" } }' }];\r\n      this.sel = 0; this.save(); this.renderRoutes(); this.renderGql(); this.loadRoute();\r\n      this.toast('Sample loaded. Turn the server on to intercept fetch.', 'info');\r\n    },\r\n\r\n    reset: function () { this.stopServer(); this.S = this.fresh(); this._seq = {}; this._state = {}; this._gid = 0; this.sel = -1; this.save(); this.renderRoutes(); this.renderGql(); this.renderEnv(); this.renderLog(); this.toast('Reset.', 'info'); },\r\n\r\n    toast: function (msg, type) { var t = document.getElementById('pth-ma-toast'); if (!t) { return; } var colors = { success: '#059669', error: '#dc2626', info: '#7c3aed' }; t.style.background = colors[type] || colors.info; t.textContent = msg; t.className = 'pth-ma-toast show'; if (this._tt) { clearTimeout(this._tt); } this._tt = setTimeout(function () { t.className = 'pth-ma-toast'; }, 3000); }\r\n  };\r\n\r\n  window.pthMockApiApp = App;\r\n\r\n  function runInit() { if (!window.pthMockApiApp.initialized && document.getElementById('pth-mockapi-wrap')) { window.pthMockApiApp.initialized = true; window.pthMockApiApp.init(); } }\r\n  runInit();\r\n  if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', runInit); }\r\n  window.addEventListener('load', function () { setTimeout(runInit, 50); });\r\n  if (!window.pthMockApiApp.initialized && typeof MutationObserver !== 'undefined') {\r\n    var obs = new MutationObserver(function () { if (document.getElementById('pth-mockapi-wrap')) { obs.disconnect(); runInit(); } });\r\n    obs.observe(document.body || document.documentElement, { childList: true, subtree: true });\r\n    setTimeout(function () { obs.disconnect(); }, 10000);\r\n  }\r\n})();\r\n<\/script>\n\n\n\n<p class=\"wp-block-paragraph\">Last Updated: August 2026<\/p>\n\n\n\n<h2 id=\"\ud83d\udfe2-build-the-front-end-before-the-back-end-exists\" class=\"wp-block-heading\">\ud83d\udfe2 Build The Front End Before The Back End Exists<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Most interface work stalls for the same reason: the screen needs data, and the endpoint that serves it is still being written. The usual workaround is a hardcoded array pasted into the component, which works right up until the real API arrives with different field names and a loading state nobody planned for.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">A mock API removes that stall. You describe the endpoints your screen expects, your code calls them normally with&nbsp;<code>fetch<\/code>, and the answers come back from inside the page. No server to install, no configuration file, nothing uploaded.<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><th>Tab<\/th><th>What it handles<\/th><\/tr><\/thead><tbody><tr><td>Mock Server<\/td><td>Route table with method, path parameters, status code, headers, delay and per-scenario bodies<\/td><\/tr><tr><td>Template<\/td><td>Live HMPL rendering against those routes, cleaned before it is shown<\/td><\/tr><tr><td>Inspector<\/td><td>Every intercepted request with timing, matched route, parameters and full response<\/td><\/tr><tr><td>Export<\/td><td>OpenAPI 3.0, Postman collection, fetch, axios and cURL snippets, plus a share link<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<h2 id=\"\ud83d\udfe1-making-your-first-route\" class=\"wp-block-heading\">\ud83d\udfe1 Making Your First Route<\/h2>\n\n\n\n<ol class=\"wp-block-list\">\n<li>Press&nbsp;<strong>Load Sample<\/strong>&nbsp;to see three working routes, or&nbsp;<strong>New Route<\/strong>&nbsp;to start clean.<\/li>\n\n\n\n<li>Set the method and path. Use a colon for anything variable:&nbsp;<code>\/api\/users\/:id<\/code>.<\/li>\n\n\n\n<li>Choose a status code and a delay. Three hundred milliseconds is closer to reality than zero.<\/li>\n\n\n\n<li>Write the response body. JSON5 is accepted, so unquoted keys and trailing commas will not stop you.<\/li>\n\n\n\n<li>Press&nbsp;<strong>Save Route<\/strong>, then&nbsp;<strong>Send Test Request<\/strong>&nbsp;to confirm it answers.<\/li>\n<\/ol>\n\n\n\n<h4 id=\"path-parameters-in-the-body\" class=\"wp-block-heading\">Path parameters in the body<\/h4>\n\n\n\n<p class=\"wp-block-paragraph\">A parameter you capture in the path can be used in the response. One route then answers for every record:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">Path:  \/api\/users\/:id\nBody:  { id: {{id}}, name: \"User {{id}}\", role: \"Member\" }\n \nGET \/api\/users\/42  -&gt;  { \"id\": 42, \"name\": \"User 42\", \"role\": \"Member\" }\nGET \/api\/users\/7   -&gt;  { \"id\": 7,  \"name\": \"User 7\",  \"role\": \"Member\" }<\/pre>\n\n\n\n<h2 id=\"\ud83d\udd34-the-feature-worth-learning-first\" class=\"wp-block-heading\">\ud83d\udd34 The Feature Worth Learning First<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">The scenario selector at the top is the part that changes how you work.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Every route carries three bodies rather than one: a success response, an empty response, and an error response with its own status code. The selector switches all of them at once.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Load the sample, leave the template rendering, and change the selector from Success to Empty. The list has no rows now &#8211; does your screen show a helpful message, or a blank rectangle that looks broken? Change it to Error and the same route answers 503. Does anything tell the user what happened?<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Interfaces are almost always built against the happy path and then meet reality in production. Three seconds of clicking a selector tests the states that actually generate support tickets: nothing to show, something went wrong, and it is taking a while.<\/p>\n\n\n\n<h2 id=\"\ud83d\udfe2-two-jobs-this-handles-well\" class=\"wp-block-heading\">\ud83d\udfe2 Two Jobs This Handles Well<\/h2>\n\n\n\n<h3 id=\"agreeing-the-shape-before-anyone-writes-code\" class=\"wp-block-heading\">Agreeing the shape before anyone writes code<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Sketch the endpoints you think the screen needs, fill in sample bodies, then export an OpenAPI specification from the Export tab. That file is a concrete thing to review with whoever is building the back end, and it is far easier to change a mock than a finished controller.<\/p>\n\n\n\n<h3 id=\"reproducing-a-slow-or-failing-endpoint\" class=\"wp-block-heading\">Reproducing a slow or failing endpoint<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">A bug that only appears when one call takes two seconds is painful to chase against a fast local server. Set the delay to 2000 on that route, reproduce it reliably, and watch the timing in the Inspector.<\/p>\n\n\n\n<h2 id=\"\ud83d\udfe1-exporting-what-you-designed\" class=\"wp-block-heading\">\ud83d\udfe1 Exporting What You Designed<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">The Export tab turns your routes into five things, none of which need a server:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>\ud83d\udd35&nbsp;<strong>OpenAPI 3.0<\/strong>&nbsp;&#8211; a specification file describing paths, parameters and example responses.<\/li>\n\n\n\n<li>\ud83d\udfe0&nbsp;<strong>Postman collection<\/strong>&nbsp;&#8211; importable straight into Postman for manual testing.<\/li>\n\n\n\n<li>\ud83d\udfe3&nbsp;<strong>fetch and axios snippets<\/strong>&nbsp;&#8211; client code for each route, ready to paste.<\/li>\n\n\n\n<li>\ud83d\udd35&nbsp;<strong>cURL commands<\/strong>&nbsp;&#8211; useful for a README or a quick terminal check.<\/li>\n\n\n\n<li>\ud83d\udfe0&nbsp;<strong>Share link<\/strong>&nbsp;&#8211; your whole setup compressed into the address itself, with nothing stored anywhere.<\/li>\n<\/ul>\n\n\n\n<h2 id=\"\ud83d\udd34-common-mistakes-and-real-limits\" class=\"wp-block-heading\">\ud83d\udd34 Common Mistakes and Real Limits<\/h2>\n\n\n\n<ul class=\"wp-block-list\">\n<li>\ud83d\udd35&nbsp;<strong>Only listed paths are intercepted.<\/strong>&nbsp;Anything else your browser requests passes through untouched. If a route seems ignored, the path or the method usually does not match.<\/li>\n\n\n\n<li>\ud83d\udfe0&nbsp;<strong>Requests use fetch, not XMLHttpRequest.<\/strong>&nbsp;Older code built on XHR will not be caught. Modern frameworks and the template engine here all use fetch.<\/li>\n\n\n\n<li>\ud83d\udfe3&nbsp;<strong>Zero delay teaches bad habits.<\/strong>&nbsp;An instant response hides every loading state. Leave a realistic delay on so spinners and skeletons actually get exercised.<\/li>\n\n\n\n<li>\ud83d\udd35&nbsp;<strong>A mock is a guess until the contract is agreed.<\/strong>&nbsp;If the real endpoint returns&nbsp;<code>user_name<\/code>&nbsp;and your mock says&nbsp;<code>name<\/code>, everything works here and breaks on connection. Export the specification and have it checked.<\/li>\n\n\n\n<li>\ud83d\udfe0&nbsp;<strong>The share link holds everything you typed.<\/strong>&nbsp;Route bodies travel inside the address, so do not paste real credentials or customer data into a link you send to someone.<\/li>\n\n\n\n<li>\ud83d\udfe3&nbsp;<strong>Rendered output is cleaned before display.<\/strong>&nbsp;Scripts inside a mock response are stripped, which is intentional. This page previews markup, it does not execute application code.<\/li>\n<\/ul>\n\n\n\n<h2 id=\"\ud83d\udfe2-where-this-sits-next-to-other-tools\" class=\"wp-block-heading\">\ud83d\udfe2 Where This Sits Next To Other Tools<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">This page designs and serves endpoints. Reshaping the JSON itself &#8211; querying it, converting it, validating it &#8211; belongs to the\u00a0<a href=\"https:\/\/schoolict.net\/tools\/free-jsonpath-evaluator-extractor\/\">JSON Studio Pro<\/a>. If you need believable filler records to put inside your responses, generate them in the\u00a0<a href=\"https:\/\/schoolict.net\/tools\/lorem-ipsum-and-json-dummy-data-generator-pro\/\">dummy data generator<\/a>\u00a0first. The background on why mocking works and where it goes wrong is in\u00a0<a href=\"https:\/\/schoolict.net\/tools\/hmpl-render-studio-article\/\">how API mocking works<\/a>.<\/p>\n\n\n\n<style>\n.pth-faq-section{margin-top:50px;margin-bottom:40px}\n.pth-faq-header{font-size:1.8rem;font-weight:800;color:#0f172a;margin-bottom:25px;border-bottom:2px solid #e2e8f0;padding-bottom:10px;display:flex;align-items:center;gap:10px}\n.pth-faq-grid{display:grid;grid-template-columns:100%;gap:18px}\n@media(min-width:768px){.pth-faq-grid{grid-template-columns:repeat(2,1fr)}}\n@media(min-width:1024px){.pth-faq-grid{grid-template-columns:repeat(3,1fr)}}\n.pth-faq-card{background:#f8fafc;padding:22px;border-radius:12px;border:1px solid #e2e8f0;transition:transform .2s ease}\n.pth-faq-card:hover{transform:translateY(-3px);box-shadow:0 4px 12px rgba(0,0,0,.05)}\n.pth-faq-q{color:#0f172a;font-size:1rem;font-weight:700;margin:0 0 10px;line-height:1.4}\n.pth-faq-a{margin:0;font-size:.92rem;color:#1e293b;line-height:1.6;font-weight:500}\n<\/style>\n \n<div class=\"pth-faq-wrap\">\n  <p class=\"pth-faq-title\">Frequently Asked Questions<\/p>\n  <div class=\"pth-faq-grid\">\n \n    <div class=\"pth-faq-card\">\n      <p class=\"pth-faq-q\">Do I need to install or run a server?<\/p>\n      <p class=\"pth-faq-a\">No. Requests are answered inside the page itself, so there is nothing to install, no port to start and no configuration file to maintain.<\/p>\n    <\/div>\n \n    <div class=\"pth-faq-card\">\n      <p class=\"pth-faq-q\">Will this interfere with other requests?<\/p>\n      <p class=\"pth-faq-a\">No. Only paths matching a route you defined are answered locally. Everything else passes through untouched, and the checkbox at the top pauses interception completely.<\/p>\n    <\/div>\n \n    <div class=\"pth-faq-card\">\n      <p class=\"pth-faq-q\">How do path parameters work?<\/p>\n      <p class=\"pth-faq-a\">Write a colon before the name, as in \/api\/users\/:id. The matched value is captured and can be placed in the response body by writing the name in double braces.<\/p>\n    <\/div>\n \n    <div class=\"pth-faq-card\">\n      <p class=\"pth-faq-q\">What does the scenario selector do?<\/p>\n      <p class=\"pth-faq-a\">Each route stores a success, empty and error response. The selector switches every route at once, so you can check how your interface handles no data and failures without editing anything.<\/p>\n    <\/div>\n \n    <div class=\"pth-faq-card\">\n      <p class=\"pth-faq-q\">Does it capture XMLHttpRequest calls?<\/p>\n      <p class=\"pth-faq-a\">No, it intercepts fetch only. Modern frameworks and the template engine on this page all use fetch, but older XHR-based code will not be caught.<\/p>\n    <\/div>\n \n    <div class=\"pth-faq-card\">\n      <p class=\"pth-faq-q\">Can I use JSON5 in the response body?<\/p>\n      <p class=\"pth-faq-a\">Yes. Unquoted keys, single quotes and trailing commas are all accepted, which saves fighting with syntax while you sketch a response.<\/p>\n    <\/div>\n \n    <div class=\"pth-faq-card\">\n      <p class=\"pth-faq-q\">What can I export?<\/p>\n      <p class=\"pth-faq-a\">An OpenAPI 3.0 specification, a Postman collection, and fetch, axios or cURL snippets for every route. There is also a share link that carries the whole setup.<\/p>\n    <\/div>\n \n    <div class=\"pth-faq-card\">\n      <p class=\"pth-faq-q\">Is the share link stored on a server?<\/p>\n      <p class=\"pth-faq-a\">No. Your routes and template are compressed into the address itself, so nothing is saved anywhere. Avoid putting real credentials in a link you send to other people.<\/p>\n    <\/div>\n \n    <div class=\"pth-faq-card\">\n      <p class=\"pth-faq-q\">Why should I set a delay?<\/p>\n      <p class=\"pth-faq-a\">An instant response hides every loading state you wrote. A few hundred milliseconds makes spinners and skeletons visible, which is the only way to know they work.<\/p>\n    <\/div>\n \n  <\/div>\n<\/div>\n","protected":false},"excerpt":{"rendered":"<p>Build, Render, and Mock APIs Without a Server Easily test and compile HMPL templates entirely offline with our advanced HMPL render utility. Features a built-in mock API server and live preview. \u00a0 Last Updated: August 2026 \ud83d\udfe2 Build The Front End Before The Back End Exists Most interface work stalls for the same reason: the &#8230; <a title=\"HMPL Render &amp; Mock API Studio \u2013 Free Offline REST &amp; GraphQL Mock Server with\" class=\"read-more\" href=\"https:\/\/schoolict.net\/tools\/hmpl-render-mock-api-studio\/\" aria-label=\"Read more about HMPL Render &amp; Mock API Studio \u2013 Free Offline REST &amp; GraphQL Mock Server with\">Read more<\/a><\/p>\n","protected":false},"author":1,"featured_media":4753,"parent":0,"menu_order":0,"comment_status":"closed","ping_status":"closed","template":"","meta":{"footnotes":""},"class_list":["post-4735","page","type-page","status-publish","has-post-thumbnail"],"_links":{"self":[{"href":"https:\/\/schoolict.net\/tools\/wp-json\/wp\/v2\/pages\/4735","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/schoolict.net\/tools\/wp-json\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/schoolict.net\/tools\/wp-json\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/schoolict.net\/tools\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/schoolict.net\/tools\/wp-json\/wp\/v2\/comments?post=4735"}],"version-history":[{"count":7,"href":"https:\/\/schoolict.net\/tools\/wp-json\/wp\/v2\/pages\/4735\/revisions"}],"predecessor-version":[{"id":7017,"href":"https:\/\/schoolict.net\/tools\/wp-json\/wp\/v2\/pages\/4735\/revisions\/7017"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/schoolict.net\/tools\/wp-json\/wp\/v2\/media\/4753"}],"wp:attachment":[{"href":"https:\/\/schoolict.net\/tools\/wp-json\/wp\/v2\/media?parent=4735"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}