@velora-cms/server 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (367) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +65 -0
  3. package/dist/admin-bundle/assets/__virtual_mf___mfe_internal__velora_admin__mf_owner__1__loadShare__react__loadShare__.js_commonjs-proxy-Ng0fOpdU.js +1 -0
  4. package/dist/admin-bundle/assets/__virtual_mf___mfe_internal__velora_admin__mf_owner__1__loadShare__react_mf_2_dom__loadShare__.js_commonjs-proxy-oc1i_iBU.js +9 -0
  5. package/dist/admin-bundle/assets/_virtual_mf-localSharedImportMap___mfe_internal__velora_admin__mf_owner__1-DOKdWYyj.js +1 -0
  6. package/dist/admin-bundle/assets/_virtual_mf___mfe_internal__velora_admin__mf_owner__1__loadShare___mf_0_velora_mf_2_cms_mf_1_plugin_mf_2_sdk__loadShare__.js-C1-ZAxwO.js +2 -0
  7. package/dist/admin-bundle/assets/_virtual_mf___mfe_internal__velora_admin__mf_owner__1__loadShare__react__loadShare__.js-BuUBskgj.js +9 -0
  8. package/dist/admin-bundle/assets/_virtual_mf___mfe_internal__velora_admin__mf_owner__1__loadShare__react_mf_1_jsx_mf_2_runtime__loadShare__.js-DMaku-HC.js +9 -0
  9. package/dist/admin-bundle/assets/_virtual_mf___mfe_internal__velora_admin__mf_owner__1__loadShare__react_mf_2_dom__loadShare__.js-DYfqn12F.js +1 -0
  10. package/dist/admin-bundle/assets/_virtual_mf___mfe_internal__velora_admin__mf_owner__1__loadShare__react_mf_2_dom_mf_1_client__loadShare__.js-BPoj3DC9.js +25 -0
  11. package/dist/admin-bundle/assets/content-trash.schema-DfXHkC-w.js +72 -0
  12. package/dist/admin-bundle/assets/hostInit--C4xK6nj.js +1 -0
  13. package/dist/admin-bundle/assets/ibm-plex-mono-cyrillic-400-normal-BSMlKf0J.woff2 +0 -0
  14. package/dist/admin-bundle/assets/ibm-plex-mono-cyrillic-400-normal-CEL4l2ZJ.woff +0 -0
  15. package/dist/admin-bundle/assets/ibm-plex-mono-cyrillic-500-normal-Ael50iVv.woff +0 -0
  16. package/dist/admin-bundle/assets/ibm-plex-mono-cyrillic-500-normal-Bq9vWWag.woff2 +0 -0
  17. package/dist/admin-bundle/assets/ibm-plex-mono-cyrillic-ext-400-normal-DMdlQ8Kv.woff +0 -0
  18. package/dist/admin-bundle/assets/ibm-plex-mono-cyrillic-ext-400-normal-xuaO2J-f.woff2 +0 -0
  19. package/dist/admin-bundle/assets/ibm-plex-mono-cyrillic-ext-500-normal-BIfNGwUT.woff +0 -0
  20. package/dist/admin-bundle/assets/ibm-plex-mono-cyrillic-ext-500-normal-BqneJy0T.woff2 +0 -0
  21. package/dist/admin-bundle/assets/ibm-plex-mono-latin-400-normal-CvHOgSBP.woff +0 -0
  22. package/dist/admin-bundle/assets/ibm-plex-mono-latin-400-normal-DMJ8VG8y.woff2 +0 -0
  23. package/dist/admin-bundle/assets/ibm-plex-mono-latin-500-normal-CB9ihrfo.woff +0 -0
  24. package/dist/admin-bundle/assets/ibm-plex-mono-latin-500-normal-DSY6xOcd.woff2 +0 -0
  25. package/dist/admin-bundle/assets/ibm-plex-mono-latin-ext-400-normal-BmRBH3aV.woff2 +0 -0
  26. package/dist/admin-bundle/assets/ibm-plex-mono-latin-ext-400-normal-D3D2R8hC.woff +0 -0
  27. package/dist/admin-bundle/assets/ibm-plex-mono-latin-ext-500-normal-CAhNIIs5.woff2 +0 -0
  28. package/dist/admin-bundle/assets/ibm-plex-mono-latin-ext-500-normal-CZ70TYgx.woff +0 -0
  29. package/dist/admin-bundle/assets/ibm-plex-mono-vietnamese-400-normal-BulugwFq.woff2 +0 -0
  30. package/dist/admin-bundle/assets/ibm-plex-mono-vietnamese-400-normal-DDuiU_S-.woff +0 -0
  31. package/dist/admin-bundle/assets/ibm-plex-mono-vietnamese-500-normal-C8zxqsMH.woff +0 -0
  32. package/dist/admin-bundle/assets/ibm-plex-mono-vietnamese-500-normal-DZ4AoWbu.woff2 +0 -0
  33. package/dist/admin-bundle/assets/ibm-plex-sans-arabic-arabic-400-normal-CyU-ddYS.woff2 +0 -0
  34. package/dist/admin-bundle/assets/ibm-plex-sans-arabic-arabic-400-normal-EA1o-u_Q.woff +0 -0
  35. package/dist/admin-bundle/assets/ibm-plex-sans-arabic-arabic-500-normal-C4MQITzh.woff2 +0 -0
  36. package/dist/admin-bundle/assets/ibm-plex-sans-arabic-arabic-500-normal-DPXcppbm.woff +0 -0
  37. package/dist/admin-bundle/assets/ibm-plex-sans-arabic-arabic-600-normal-0pRdybE_.woff2 +0 -0
  38. package/dist/admin-bundle/assets/ibm-plex-sans-arabic-arabic-600-normal-MJZ0liF5.woff +0 -0
  39. package/dist/admin-bundle/assets/ibm-plex-sans-arabic-latin-400-normal-Bo5KPYvw.woff2 +0 -0
  40. package/dist/admin-bundle/assets/ibm-plex-sans-arabic-latin-400-normal-CFZh1unN.woff +0 -0
  41. package/dist/admin-bundle/assets/ibm-plex-sans-arabic-latin-500-normal-BletK1Hp.woff +0 -0
  42. package/dist/admin-bundle/assets/ibm-plex-sans-arabic-latin-500-normal-Cd6jVIg7.woff2 +0 -0
  43. package/dist/admin-bundle/assets/ibm-plex-sans-arabic-latin-600-normal-Dk5eISD_.woff +0 -0
  44. package/dist/admin-bundle/assets/ibm-plex-sans-arabic-latin-600-normal-KrqB56Mw.woff2 +0 -0
  45. package/dist/admin-bundle/assets/ibm-plex-sans-arabic-latin-ext-400-normal-BeVLg08x.woff +0 -0
  46. package/dist/admin-bundle/assets/ibm-plex-sans-arabic-latin-ext-400-normal-BpLfq-63.woff2 +0 -0
  47. package/dist/admin-bundle/assets/ibm-plex-sans-arabic-latin-ext-500-normal-Ck_W4G78.woff2 +0 -0
  48. package/dist/admin-bundle/assets/ibm-plex-sans-arabic-latin-ext-500-normal-eJxPnL1P.woff +0 -0
  49. package/dist/admin-bundle/assets/ibm-plex-sans-arabic-latin-ext-600-normal-DT29Q8HW.woff +0 -0
  50. package/dist/admin-bundle/assets/ibm-plex-sans-arabic-latin-ext-600-normal-DjDAeNQD.woff2 +0 -0
  51. package/dist/admin-bundle/assets/ibm-plex-sans-cyrillic-400-normal-BTotfTJu.woff +0 -0
  52. package/dist/admin-bundle/assets/ibm-plex-sans-cyrillic-400-normal-DZqxrq2p.woff2 +0 -0
  53. package/dist/admin-bundle/assets/ibm-plex-sans-cyrillic-500-normal-ByOcLdNv.woff +0 -0
  54. package/dist/admin-bundle/assets/ibm-plex-sans-cyrillic-500-normal-CocWQlwt.woff2 +0 -0
  55. package/dist/admin-bundle/assets/ibm-plex-sans-cyrillic-600-normal-71GNu3SW.woff2 +0 -0
  56. package/dist/admin-bundle/assets/ibm-plex-sans-cyrillic-600-normal-BGq0mW3O.woff +0 -0
  57. package/dist/admin-bundle/assets/ibm-plex-sans-cyrillic-ext-400-normal-Dsrv2Tcn.woff +0 -0
  58. package/dist/admin-bundle/assets/ibm-plex-sans-cyrillic-ext-400-normal-g30qAdWV.woff2 +0 -0
  59. package/dist/admin-bundle/assets/ibm-plex-sans-cyrillic-ext-500-normal-Cs5J6C77.woff2 +0 -0
  60. package/dist/admin-bundle/assets/ibm-plex-sans-cyrillic-ext-500-normal-DB5PtV2g.woff +0 -0
  61. package/dist/admin-bundle/assets/ibm-plex-sans-cyrillic-ext-600-normal-Bz0x94Yp.woff +0 -0
  62. package/dist/admin-bundle/assets/ibm-plex-sans-cyrillic-ext-600-normal-DUMzJB7m.woff2 +0 -0
  63. package/dist/admin-bundle/assets/ibm-plex-sans-greek-400-normal-D9ESIMu3.woff +0 -0
  64. package/dist/admin-bundle/assets/ibm-plex-sans-greek-400-normal-_efipK4i.woff2 +0 -0
  65. package/dist/admin-bundle/assets/ibm-plex-sans-greek-500-normal-CuWXN6rf.woff +0 -0
  66. package/dist/admin-bundle/assets/ibm-plex-sans-greek-500-normal-JMMifIXV.woff2 +0 -0
  67. package/dist/admin-bundle/assets/ibm-plex-sans-greek-600-normal-D-CqTdkO.woff +0 -0
  68. package/dist/admin-bundle/assets/ibm-plex-sans-greek-600-normal-DzTrcv_p.woff2 +0 -0
  69. package/dist/admin-bundle/assets/ibm-plex-sans-latin-400-normal-CDDApCn2.woff2 +0 -0
  70. package/dist/admin-bundle/assets/ibm-plex-sans-latin-400-normal-CYLoc0-x.woff +0 -0
  71. package/dist/admin-bundle/assets/ibm-plex-sans-latin-500-normal-6ng42L7E.woff2 +0 -0
  72. package/dist/admin-bundle/assets/ibm-plex-sans-latin-500-normal-BgVn5rGT.woff +0 -0
  73. package/dist/admin-bundle/assets/ibm-plex-sans-latin-600-normal-Cu4Hd6ag.woff +0 -0
  74. package/dist/admin-bundle/assets/ibm-plex-sans-latin-600-normal-CuJfVYMP.woff2 +0 -0
  75. package/dist/admin-bundle/assets/ibm-plex-sans-latin-ext-400-normal-C5H60-Va.woff2 +0 -0
  76. package/dist/admin-bundle/assets/ibm-plex-sans-latin-ext-400-normal-RBey6euL.woff +0 -0
  77. package/dist/admin-bundle/assets/ibm-plex-sans-latin-ext-500-normal-D0aIdm-b.woff +0 -0
  78. package/dist/admin-bundle/assets/ibm-plex-sans-latin-ext-500-normal-DakdToA3.woff2 +0 -0
  79. package/dist/admin-bundle/assets/ibm-plex-sans-latin-ext-600-normal-DIrixKbi.woff +0 -0
  80. package/dist/admin-bundle/assets/ibm-plex-sans-latin-ext-600-normal-DOrvGEcy.woff2 +0 -0
  81. package/dist/admin-bundle/assets/ibm-plex-sans-vietnamese-400-normal-DG4YqDda.woff2 +0 -0
  82. package/dist/admin-bundle/assets/ibm-plex-sans-vietnamese-400-normal-fK1oJ5dG.woff +0 -0
  83. package/dist/admin-bundle/assets/ibm-plex-sans-vietnamese-500-normal-BEb3_waV.woff +0 -0
  84. package/dist/admin-bundle/assets/ibm-plex-sans-vietnamese-500-normal-e4dixQRQ.woff2 +0 -0
  85. package/dist/admin-bundle/assets/ibm-plex-sans-vietnamese-600-normal-DgdngZtN.woff +0 -0
  86. package/dist/admin-bundle/assets/ibm-plex-sans-vietnamese-600-normal-DpPYBSTl.woff2 +0 -0
  87. package/dist/admin-bundle/assets/index-BIAF0ShP.js +542 -0
  88. package/dist/admin-bundle/assets/index-DhMY-0sC.js +148 -0
  89. package/dist/admin-bundle/assets/index-DnZV_gsg.css +1 -0
  90. package/dist/admin-bundle/assets/index-Do71v6NZ.js +7 -0
  91. package/dist/admin-bundle/assets/literata-cyrillic-ext-opsz-italic-5yUt4G_9.woff2 +0 -0
  92. package/dist/admin-bundle/assets/literata-cyrillic-ext-opsz-normal-C5tfwALZ.woff2 +0 -0
  93. package/dist/admin-bundle/assets/literata-cyrillic-opsz-italic-Bj0ndbyp.woff2 +0 -0
  94. package/dist/admin-bundle/assets/literata-cyrillic-opsz-normal-DgmypKXX.woff2 +0 -0
  95. package/dist/admin-bundle/assets/literata-greek-ext-opsz-italic-DFpO5j-Z.woff2 +0 -0
  96. package/dist/admin-bundle/assets/literata-greek-ext-opsz-normal-DGm64vGR.woff2 +0 -0
  97. package/dist/admin-bundle/assets/literata-greek-opsz-italic-CHERNRFq.woff2 +0 -0
  98. package/dist/admin-bundle/assets/literata-greek-opsz-normal-DFglUmAd.woff2 +0 -0
  99. package/dist/admin-bundle/assets/literata-latin-ext-opsz-italic-4y8iAqqs.woff2 +0 -0
  100. package/dist/admin-bundle/assets/literata-latin-ext-opsz-normal-STiTPs0F.woff2 +0 -0
  101. package/dist/admin-bundle/assets/literata-latin-opsz-italic-CJ93D38p.woff2 +0 -0
  102. package/dist/admin-bundle/assets/literata-latin-opsz-normal-BHZsWIlc.woff2 +0 -0
  103. package/dist/admin-bundle/assets/literata-vietnamese-opsz-italic-BdcrZMcO.woff2 +0 -0
  104. package/dist/admin-bundle/assets/literata-vietnamese-opsz-normal-Gqw5JQlq.woff2 +0 -0
  105. package/dist/admin-bundle/assets/mf-entry-bootstrap-0-aef015cd.js +41 -0
  106. package/dist/admin-bundle/assets/remoteEntry-CNyN68bc.js +14 -0
  107. package/dist/admin-bundle/assets/virtualExposes-BDCSo5OS.js +1 -0
  108. package/dist/admin-bundle/assets/vite-preload-helper-Cgledu0r.js +1 -0
  109. package/dist/admin-bundle/index.html +16 -0
  110. package/dist/app.d.ts +8 -0
  111. package/dist/app.js +252 -0
  112. package/dist/auth/api-content-author.d.ts +2 -0
  113. package/dist/auth/api-content-author.js +27 -0
  114. package/dist/auth/api-keys.d.ts +7 -0
  115. package/dist/auth/api-keys.js +85 -0
  116. package/dist/auth/jwt-payload.d.ts +16 -0
  117. package/dist/auth/jwt-payload.js +1 -0
  118. package/dist/auth/preview-tokens.d.ts +7 -0
  119. package/dist/auth/preview-tokens.js +76 -0
  120. package/dist/auth/rate-limit.d.ts +16 -0
  121. package/dist/auth/rate-limit.js +283 -0
  122. package/dist/auth/require-auth.d.ts +4 -0
  123. package/dist/auth/require-auth.js +28 -0
  124. package/dist/auth/route-rate-limits.d.ts +31 -0
  125. package/dist/auth/route-rate-limits.js +155 -0
  126. package/dist/auth/tokens.d.ts +15 -0
  127. package/dist/auth/tokens.js +38 -0
  128. package/dist/bin.d.ts +2 -0
  129. package/dist/bin.js +6 -0
  130. package/dist/boot/core-migration-gate.d.ts +12 -0
  131. package/dist/boot/core-migration-gate.js +52 -0
  132. package/dist/boot/resolve-boot-mode.d.ts +6 -0
  133. package/dist/boot/resolve-boot-mode.js +206 -0
  134. package/dist/bundled-templates/com.velora.blog/manifest.json +39 -0
  135. package/dist/bundled-templates/com.velora.blog/package.tar.gz +0 -0
  136. package/dist/bundled-templates/com.velora.blog/sample-content.json +14 -0
  137. package/dist/config/admin-ui.d.ts +1 -0
  138. package/dist/config/admin-ui.js +10 -0
  139. package/dist/config/cors.d.ts +7 -0
  140. package/dist/config/cors.js +53 -0
  141. package/dist/config/env-file-path.d.ts +2 -0
  142. package/dist/config/env-file-path.js +22 -0
  143. package/dist/config/graphql-limits.d.ts +2 -0
  144. package/dist/config/graphql-limits.js +26 -0
  145. package/dist/config/headless.d.ts +1 -0
  146. package/dist/config/headless.js +8 -0
  147. package/dist/config/license-revalidation.d.ts +2 -0
  148. package/dist/config/license-revalidation.js +16 -0
  149. package/dist/config/load-env.d.ts +1 -0
  150. package/dist/config/load-env.js +53 -0
  151. package/dist/config/marketplace.d.ts +11 -0
  152. package/dist/config/marketplace.js +19 -0
  153. package/dist/config/preview.d.ts +3 -0
  154. package/dist/config/preview.js +39 -0
  155. package/dist/config/public-docs.d.ts +1 -0
  156. package/dist/config/public-docs.js +9 -0
  157. package/dist/config/site.d.ts +7 -0
  158. package/dist/config/site.js +12 -0
  159. package/dist/config/trust-proxy.d.ts +2 -0
  160. package/dist/config/trust-proxy.js +42 -0
  161. package/dist/data-types/validate-data-type-settings.d.ts +1 -0
  162. package/dist/data-types/validate-data-type-settings.js +33 -0
  163. package/dist/db/content-db.d.ts +16 -0
  164. package/dist/db/content-db.js +109 -0
  165. package/dist/db/core-migrations/index.d.ts +14 -0
  166. package/dist/db/core-migrations/index.js +184 -0
  167. package/dist/db/core-migrations/steps.d.ts +4 -0
  168. package/dist/db/core-migrations/steps.js +66 -0
  169. package/dist/db/load-prisma-client.d.ts +2 -0
  170. package/dist/db/load-prisma-client.js +44 -0
  171. package/dist/db/prisma.d.ts +3 -0
  172. package/dist/db/prisma.js +67 -0
  173. package/dist/db/resolve-client-dir.d.ts +2 -0
  174. package/dist/db/resolve-client-dir.js +83 -0
  175. package/dist/docs/filter-public-api-spec.d.ts +2 -0
  176. package/dist/docs/filter-public-api-spec.js +52 -0
  177. package/dist/docs-bundle/404.html +41 -0
  178. package/dist/docs-bundle/_astro/MobileTableOfContents.astro_astro_type_script_index_0_lang.BcSo_yiZ.js +1 -0
  179. package/dist/docs-bundle/_astro/Search.astro_astro_type_script_index_0_lang.CavldbAs.js +1 -0
  180. package/dist/docs-bundle/_astro/TableOfContents.astro_astro_type_script_index_0_lang.Csloo1VZ.js +1 -0
  181. package/dist/docs-bundle/_astro/common.DTXA7sXM.css +3 -0
  182. package/dist/docs-bundle/_astro/ec.0vx5m.js +3 -0
  183. package/dist/docs-bundle/_astro/ec.w36nc.css +1 -0
  184. package/dist/docs-bundle/_astro/page.Dwipeu-R.js +1 -0
  185. package/dist/docs-bundle/_astro/print.ehPL0gv-.css +1 -0
  186. package/dist/docs-bundle/_astro/starlight-toc.CTCQXEge.js +1 -0
  187. package/dist/docs-bundle/_astro/ui-core.D-7Scnhs.js +2 -0
  188. package/dist/docs-bundle/favicon.svg +1 -0
  189. package/dist/docs-bundle/index.html +1 -0
  190. package/dist/docs-bundle/pagefind/fragment/en_18a36d7.pf_fragment +0 -0
  191. package/dist/docs-bundle/pagefind/fragment/en_1d9eb24.pf_fragment +0 -0
  192. package/dist/docs-bundle/pagefind/fragment/en_1ef320c.pf_fragment +0 -0
  193. package/dist/docs-bundle/pagefind/fragment/en_3099406.pf_fragment +0 -0
  194. package/dist/docs-bundle/pagefind/fragment/en_4a5b6fe.pf_fragment +0 -0
  195. package/dist/docs-bundle/pagefind/fragment/en_9aa652e.pf_fragment +0 -0
  196. package/dist/docs-bundle/pagefind/fragment/en_baccdf6.pf_fragment +0 -0
  197. package/dist/docs-bundle/pagefind/fragment/en_d81f3fe.pf_fragment +0 -0
  198. package/dist/docs-bundle/pagefind/fragment/en_d855550.pf_fragment +0 -0
  199. package/dist/docs-bundle/pagefind/fragment/en_e6d88d8.pf_fragment +0 -0
  200. package/dist/docs-bundle/pagefind/fragment/en_e9d3189.pf_fragment +0 -0
  201. package/dist/docs-bundle/pagefind/index/en_5feeed7.pf_index +0 -0
  202. package/dist/docs-bundle/pagefind/index/en_c6981f9.pf_index +0 -0
  203. package/dist/docs-bundle/pagefind/index/en_ce4f6bf.pf_index +0 -0
  204. package/dist/docs-bundle/pagefind/pagefind-component-ui.css +1509 -0
  205. package/dist/docs-bundle/pagefind/pagefind-component-ui.js +56 -0
  206. package/dist/docs-bundle/pagefind/pagefind-entry.json +1 -0
  207. package/dist/docs-bundle/pagefind/pagefind-highlight.js +1070 -0
  208. package/dist/docs-bundle/pagefind/pagefind-modular-ui.css +214 -0
  209. package/dist/docs-bundle/pagefind/pagefind-modular-ui.js +8 -0
  210. package/dist/docs-bundle/pagefind/pagefind-ui.css +1 -0
  211. package/dist/docs-bundle/pagefind/pagefind-ui.js +2 -0
  212. package/dist/docs-bundle/pagefind/pagefind-worker.js +6 -0
  213. package/dist/docs-bundle/pagefind/pagefind.en_1e94abd852.pf_meta +0 -0
  214. package/dist/docs-bundle/pagefind/pagefind.js +6 -0
  215. package/dist/docs-bundle/pagefind/wasm.en.pagefind +0 -0
  216. package/dist/docs-bundle/pagefind/wasm.unknown.pagefind +0 -0
  217. package/dist/docs-bundle/v1/index.html +86 -0
  218. package/dist/docs-bundle/v1/using-velora/build-a-headless-frontend/index.html +413 -0
  219. package/dist/docs-bundle/v1/using-velora/content-model/index.html +858 -0
  220. package/dist/docs-bundle/v1/using-velora/editing/index.html +748 -0
  221. package/dist/docs-bundle/v1/using-velora/getting-started/index.html +588 -0
  222. package/dist/docs-bundle/v1/using-velora/headless-api/index.html +1620 -0
  223. package/dist/docs-bundle/v1/using-velora/index.html +215 -0
  224. package/dist/docs-bundle/v1/using-velora/installation/index.html +886 -0
  225. package/dist/docs-bundle/v1/using-velora/installing-plugins/index.html +836 -0
  226. package/dist/docs-bundle/v1/using-velora/media/index.html +468 -0
  227. package/dist/docs-bundle/v1/using-velora/users-and-roles/index.html +561 -0
  228. package/dist/graphql/field-count-rule.d.ts +5 -0
  229. package/dist/graphql/field-count-rule.js +59 -0
  230. package/dist/graphql/schema.d.ts +12 -0
  231. package/dist/graphql/schema.js +330 -0
  232. package/dist/index.d.ts +1 -0
  233. package/dist/index.js +180 -0
  234. package/dist/lib.d.ts +2 -0
  235. package/dist/lib.js +39 -0
  236. package/dist/marketplace/install-bundle-members.d.ts +33 -0
  237. package/dist/marketplace/install-bundle-members.js +137 -0
  238. package/dist/marketplace/install-from-marketplace.d.ts +60 -0
  239. package/dist/marketplace/install-from-marketplace.js +581 -0
  240. package/dist/marketplace/license-revalidation-job.d.ts +38 -0
  241. package/dist/marketplace/license-revalidation-job.js +139 -0
  242. package/dist/marketplace/marketplace-client.d.ts +43 -0
  243. package/dist/marketplace/marketplace-client.js +228 -0
  244. package/dist/marketplace/revalidate-license.d.ts +39 -0
  245. package/dist/marketplace/revalidate-license.js +103 -0
  246. package/dist/media/media-service.d.ts +36 -0
  247. package/dist/media/media-service.js +162 -0
  248. package/dist/plugins/boot-plugin-loader.d.ts +32 -0
  249. package/dist/plugins/boot-plugin-loader.js +57 -0
  250. package/dist/plugins/hook-dispatcher.d.ts +13 -0
  251. package/dist/plugins/hook-dispatcher.js +68 -0
  252. package/dist/plugins/install-built-package.d.ts +36 -0
  253. package/dist/plugins/install-built-package.js +199 -0
  254. package/dist/plugins/install-package-tarball.d.ts +25 -0
  255. package/dist/plugins/install-package-tarball.js +73 -0
  256. package/dist/plugins/plugin-context.d.ts +5 -0
  257. package/dist/plugins/plugin-context.js +26 -0
  258. package/dist/plugins/plugin-package-store.d.ts +31 -0
  259. package/dist/plugins/plugin-package-store.js +64 -0
  260. package/dist/plugins/plugin-registry-service.d.ts +227 -0
  261. package/dist/plugins/plugin-registry-service.js +636 -0
  262. package/dist/plugins/seed-template-content.d.ts +23 -0
  263. package/dist/plugins/seed-template-content.js +198 -0
  264. package/dist/plugins/server-serializers.d.ts +3 -0
  265. package/dist/plugins/server-serializers.js +16 -0
  266. package/dist/plugins/simple-storage-api.d.ts +9 -0
  267. package/dist/plugins/simple-storage-api.js +26 -0
  268. package/dist/plugins/snapshot-groups.d.ts +16 -0
  269. package/dist/plugins/snapshot-groups.js +81 -0
  270. package/dist/plugins/structured-query-builder.d.ts +21 -0
  271. package/dist/plugins/structured-query-builder.js +60 -0
  272. package/dist/plugins/structured-snapshot-service.d.ts +16 -0
  273. package/dist/plugins/structured-snapshot-service.js +286 -0
  274. package/dist/plugins/structured-storage-api.d.ts +22 -0
  275. package/dist/plugins/structured-storage-api.js +91 -0
  276. package/dist/plugins/structured-storage-transaction.d.ts +22 -0
  277. package/dist/plugins/structured-storage-transaction.js +63 -0
  278. package/dist/plugins/structured-table-naming.d.ts +1 -0
  279. package/dist/plugins/structured-table-naming.js +18 -0
  280. package/dist/plugins/trusted-keys.d.ts +11 -0
  281. package/dist/plugins/trusted-keys.js +67 -0
  282. package/dist/plugins/trusted-signing-keys.d.ts +27 -0
  283. package/dist/plugins/trusted-signing-keys.js +40 -0
  284. package/dist/plugins/uninstall-bundle.d.ts +29 -0
  285. package/dist/plugins/uninstall-bundle.js +150 -0
  286. package/dist/plugins/verify-package.d.ts +36 -0
  287. package/dist/plugins/verify-package.js +121 -0
  288. package/dist/routes/admin-static.d.ts +8 -0
  289. package/dist/routes/admin-static.js +196 -0
  290. package/dist/routes/api-keys.d.ts +2 -0
  291. package/dist/routes/api-keys.js +95 -0
  292. package/dist/routes/api-playground.d.ts +2 -0
  293. package/dist/routes/api-playground.js +21 -0
  294. package/dist/routes/auth.d.ts +2 -0
  295. package/dist/routes/auth.js +102 -0
  296. package/dist/routes/content.d.ts +3 -0
  297. package/dist/routes/content.js +471 -0
  298. package/dist/routes/data-types.d.ts +3 -0
  299. package/dist/routes/data-types.js +100 -0
  300. package/dist/routes/docs-static.d.ts +8 -0
  301. package/dist/routes/docs-static.js +137 -0
  302. package/dist/routes/document-types.d.ts +3 -0
  303. package/dist/routes/document-types.js +149 -0
  304. package/dist/routes/headless-bridge.d.ts +10 -0
  305. package/dist/routes/headless-bridge.js +207 -0
  306. package/dist/routes/health.d.ts +2 -0
  307. package/dist/routes/health.js +15 -0
  308. package/dist/routes/marketplace-proxy.d.ts +8 -0
  309. package/dist/routes/marketplace-proxy.js +135 -0
  310. package/dist/routes/media.d.ts +6 -0
  311. package/dist/routes/media.js +93 -0
  312. package/dist/routes/plugin-api-routes.d.ts +12 -0
  313. package/dist/routes/plugin-api-routes.js +224 -0
  314. package/dist/routes/plugin-assets.d.ts +8 -0
  315. package/dist/routes/plugin-assets.js +51 -0
  316. package/dist/routes/plugin-management.d.ts +7 -0
  317. package/dist/routes/plugin-management.js +525 -0
  318. package/dist/routes/plugin-storage.d.ts +8 -0
  319. package/dist/routes/plugin-storage.js +104 -0
  320. package/dist/routes/plugins.d.ts +2 -0
  321. package/dist/routes/plugins.js +36 -0
  322. package/dist/routes/public-api.d.ts +9 -0
  323. package/dist/routes/public-api.js +545 -0
  324. package/dist/routes/published-item.d.ts +57 -0
  325. package/dist/routes/published-item.js +236 -0
  326. package/dist/routes/setup.d.ts +9 -0
  327. package/dist/routes/setup.js +477 -0
  328. package/dist/routes/snapshots.d.ts +3 -0
  329. package/dist/routes/snapshots.js +65 -0
  330. package/dist/routes/templates.d.ts +3 -0
  331. package/dist/routes/templates.js +37 -0
  332. package/dist/routes/themes.d.ts +2 -0
  333. package/dist/routes/themes.js +30 -0
  334. package/dist/routes/users.d.ts +2 -0
  335. package/dist/routes/users.js +113 -0
  336. package/dist/sandbox/register-local-plugin.d.ts +36 -0
  337. package/dist/sandbox/register-local-plugin.js +108 -0
  338. package/dist/sandbox/seed-sandbox.d.ts +20 -0
  339. package/dist/sandbox/seed-sandbox.js +122 -0
  340. package/dist/scripts/copy-admin-bundle.d.ts +1 -0
  341. package/dist/scripts/copy-admin-bundle.js +24 -0
  342. package/dist/scripts/copy-docs-bundle.d.ts +1 -0
  343. package/dist/scripts/copy-docs-bundle.js +22 -0
  344. package/dist/scripts/create-api-key.d.ts +5 -0
  345. package/dist/scripts/create-api-key.js +56 -0
  346. package/dist/scripts/generate-prisma-clients.d.ts +6 -0
  347. package/dist/scripts/generate-prisma-clients.js +125 -0
  348. package/dist/scripts/pack-bundled-templates.d.ts +1 -0
  349. package/dist/scripts/pack-bundled-templates.js +87 -0
  350. package/dist/setup/build-connection-string.d.ts +19 -0
  351. package/dist/setup/build-connection-string.js +14 -0
  352. package/dist/setup/bundled-templates.d.ts +19 -0
  353. package/dist/setup/bundled-templates.js +121 -0
  354. package/dist/setup/ensure-prisma-client.d.ts +3 -0
  355. package/dist/setup/ensure-prisma-client.js +23 -0
  356. package/dist/setup/install-starter.d.ts +14 -0
  357. package/dist/setup/install-starter.js +76 -0
  358. package/dist/setup/normalize-database-url.d.ts +2 -0
  359. package/dist/setup/normalize-database-url.js +35 -0
  360. package/dist/setup/persist-env-config.d.ts +2 -0
  361. package/dist/setup/persist-env-config.js +63 -0
  362. package/dist/setup/provision-database.d.ts +2 -0
  363. package/dist/setup/provision-database.js +89 -0
  364. package/dist/setup-app.d.ts +4 -0
  365. package/dist/setup-app.js +43 -0
  366. package/package.json +96 -0
  367. package/prisma/schema.prisma +303 -0
@@ -0,0 +1,1620 @@
1
+ <!DOCTYPE html><html lang="en" dir="ltr" data-theme="dark" data-has-toc data-has-sidebar class="astro-ewhz5xxr"><head><meta charset="utf-8"/><meta name="viewport" content="width=device-width, initial-scale=1"/><title>Headless API | Velora</title><link rel="shortcut icon" href="/docs/favicon.svg" type="image/svg+xml"/><meta name="generator" content="Astro v7.2.10"/><meta name="generator" content="Starlight v0.41.5"/><meta property="og:title" content="Headless API"/><meta property="og:type" content="article"/><meta property="og:locale" content="en"/><meta property="og:description" content="The complete /api/v1 reference — every route, parameter, response property, error body and limit, with live transcripts from the public demo."/><meta property="og:site_name" content="Velora"/><meta name="twitter:card" content="summary_large_image"/><meta name="description" content="The complete /api/v1 reference — every route, parameter, response property, error body and limit, with live transcripts from the public demo."/><script>
2
+ window.StarlightThemeProvider = (() => {
3
+ const storedTheme =
4
+ typeof localStorage !== 'undefined' && localStorage.getItem('starlight-theme');
5
+ const theme =
6
+ storedTheme ||
7
+ (window.matchMedia('(prefers-color-scheme: light)').matches ? 'light' : 'dark');
8
+ document.documentElement.dataset.theme = theme === 'light' ? 'light' : 'dark';
9
+ return {
10
+ updatePickers(theme = storedTheme || 'auto') {
11
+ document.querySelectorAll('starlight-theme-select').forEach((picker) => {
12
+ const select = picker.querySelector('select');
13
+ if (select) select.value = theme;
14
+ /** @type {HTMLTemplateElement | null} */
15
+ const tmpl = document.querySelector(`#theme-icons`);
16
+ const newIcon = tmpl && tmpl.content.querySelector('.' + theme);
17
+ if (newIcon) {
18
+ const oldIcon = picker.querySelector('svg.label-icon');
19
+ if (oldIcon) {
20
+ oldIcon.replaceChildren(...newIcon.cloneNode(true).childNodes);
21
+ }
22
+ }
23
+ });
24
+ },
25
+ };
26
+ })();
27
+ </script><template id="theme-icons"><svg aria-hidden="true" class="light astro-24n5ceuf" width="16" height="16" viewBox="0 0 24 24" fill="currentColor" style="--sl-icon-size: 1em;"><path d="M5 12a1 1 0 0 0-1-1H3a1 1 0 0 0 0 2h1a1 1 0 0 0 1-1Zm.64 5-.71.71a1 1 0 0 0 0 1.41 1 1 0 0 0 1.41 0l.71-.71A1 1 0 0 0 5.64 17ZM12 5a1 1 0 0 0 1-1V3a1 1 0 0 0-2 0v1a1 1 0 0 0 1 1Zm5.66 2.34a1 1 0 0 0 .7-.29l.71-.71a1 1 0 1 0-1.41-1.41l-.66.71a1 1 0 0 0 0 1.41 1 1 0 0 0 .66.29Zm-12-.29a1 1 0 0 0 1.41 0 1 1 0 0 0 0-1.41l-.71-.71a1.004 1.004 0 1 0-1.43 1.41l.73.71ZM21 11h-1a1 1 0 0 0 0 2h1a1 1 0 0 0 0-2Zm-2.64 6A1 1 0 0 0 17 18.36l.71.71a1 1 0 0 0 1.41 0 1 1 0 0 0 0-1.41l-.76-.66ZM12 6.5a5.5 5.5 0 1 0 5.5 5.5A5.51 5.51 0 0 0 12 6.5Zm0 9a3.5 3.5 0 1 1 0-7 3.5 3.5 0 0 1 0 7Zm0 3.5a1 1 0 0 0-1 1v1a1 1 0 0 0 2 0v-1a1 1 0 0 0-1-1Z"/></svg><svg aria-hidden="true" class="dark astro-24n5ceuf" width="16" height="16" viewBox="0 0 24 24" fill="currentColor" style="--sl-icon-size: 1em;"><path d="M21.64 13a1 1 0 0 0-1.05-.14 8.049 8.049 0 0 1-3.37.73 8.15 8.15 0 0 1-8.14-8.1 8.59 8.59 0 0 1 .25-2A1 1 0 0 0 8 2.36a10.14 10.14 0 1 0 14 11.69 1 1 0 0 0-.36-1.05Zm-9.5 6.69A8.14 8.14 0 0 1 7.08 5.22v.27a10.15 10.15 0 0 0 10.14 10.14 9.784 9.784 0 0 0 2.1-.22 8.11 8.11 0 0 1-7.18 4.32v-.04Z"/></svg><svg aria-hidden="true" class="auto astro-24n5ceuf" width="16" height="16" viewBox="0 0 24 24" fill="currentColor" style="--sl-icon-size: 1em;"><path d="M21 14h-1V7a3 3 0 0 0-3-3H7a3 3 0 0 0-3 3v7H3a1 1 0 0 0-1 1v2a3 3 0 0 0 3 3h14a3 3 0 0 0 3-3v-2a1 1 0 0 0-1-1ZM6 7a1 1 0 0 1 1-1h10a1 1 0 0 1 1 1v7H6V7Zm14 10a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1v-1h16v1Z"/></svg></template><link rel="stylesheet" href="/docs/_astro/print.ehPL0gv-.css" media="print"><link rel="stylesheet" href="/docs/_astro/common.DTXA7sXM.css"><script type="module" src="/docs/_astro/page.Dwipeu-R.js"></script></head><body class="astro-ewhz5xxr"><a class="sl-skip-link astro-zufphfvp" href="#_top">Skip to content</a><div class="page sl-flex astro-5auw3bkx"><header class="header astro-5auw3bkx"><div class="header astro-6ytusalw"><div class="title-wrapper sl-flex astro-6ytusalw"><a href="/docs/" class="site-title sl-flex astro-nu4hftpf"><span class="astro-nu4hftpf" translate="no">Velora</span></a></div><div class="sl-flex print:hidden astro-6ytusalw"><site-search class="astro-6ytusalw astro-zwj6qr56" data-translations="{&quot;placeholder&quot;:&quot;Search&quot;}"><button data-open-modal disabled aria-label="Search" aria-keyshortcuts="Control+K" class="astro-zwj6qr56"><svg aria-hidden="true" class="astro-zwj6qr56 astro-24n5ceuf" width="16" height="16" viewBox="0 0 24 24" fill="currentColor" style="--sl-icon-size: 1em;"><path d="M21.71 20.29 18 16.61A9 9 0 1 0 16.61 18l3.68 3.68a.999.999 0 0 0 1.42 0 1 1 0 0 0 0-1.39ZM11 18a7 7 0 1 1 0-14 7 7 0 0 1 0 14Z"/></svg><span class="sl-hidden md:sl-block astro-zwj6qr56" aria-hidden="true">Search</span><kbd class="sl-hidden md:sl-flex astro-zwj6qr56" style="display: none;"><kbd class="astro-zwj6qr56">Ctrl</kbd><kbd class="astro-zwj6qr56">K</kbd></kbd></button><dialog style="padding:0" aria-label="Search" class="astro-zwj6qr56"><div class="dialog-frame sl-flex astro-zwj6qr56"><button data-close-modal class="sl-flex md:sl-hidden astro-zwj6qr56">Cancel</button><div class="search-container astro-zwj6qr56"><div id="starlight__search" class="astro-zwj6qr56"></div></div></div></dialog></site-search><script>
28
+ (() => {
29
+ const openBtn = document.querySelector('button[data-open-modal]');
30
+ const shortcut = openBtn?.querySelector('kbd');
31
+ if (!openBtn || !(shortcut instanceof HTMLElement)) return;
32
+ const platformKey = shortcut.querySelector('kbd');
33
+ if (platformKey && /(Mac|iPhone|iPod|iPad)/i.test(navigator.platform)) {
34
+ platformKey.textContent = '⌘';
35
+ openBtn.setAttribute('aria-keyshortcuts', 'Meta+K');
36
+ }
37
+ shortcut.style.display = '';
38
+ })();
39
+ </script><script type="module" src="/docs/_astro/Search.astro_astro_type_script_index_0_lang.CavldbAs.js"></script></div><div class="sl-hidden md:sl-flex print:hidden right-group astro-6ytusalw"><div class="sl-flex social-icons astro-6ytusalw"></div><starlight-theme-select><label style="--sl-select-width: 6.25em" class="astro-7uz3f5wr"><span class="sr-only astro-7uz3f5wr">Select theme</span><svg aria-hidden="true" class="icon label-icon astro-7uz3f5wr astro-24n5ceuf" width="16" height="16" viewBox="0 0 24 24" fill="currentColor" style="--sl-icon-size: 1em;"><path d="M21 14h-1V7a3 3 0 0 0-3-3H7a3 3 0 0 0-3 3v7H3a1 1 0 0 0-1 1v2a3 3 0 0 0 3 3h14a3 3 0 0 0 3-3v-2a1 1 0 0 0-1-1ZM6 7a1 1 0 0 1 1-1h10a1 1 0 0 1 1 1v7H6V7Zm14 10a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1v-1h16v1Z"/></svg><select autocomplete="off" class="astro-7uz3f5wr"><option value="dark" class="astro-7uz3f5wr">Dark</option><option value="light" class="astro-7uz3f5wr">Light</option><option value="auto" selected class="astro-7uz3f5wr">Auto</option></select><svg aria-hidden="true" class="icon caret astro-7uz3f5wr astro-24n5ceuf" width="16" height="16" viewBox="0 0 24 24" fill="currentColor" style="--sl-icon-size: 1em;"><path d="M17 9.17a1 1 0 0 0-1.41 0L12 12.71 8.46 9.17a1 1 0 1 0-1.41 1.42l4.24 4.24a1.002 1.002 0 0 0 1.42 0L17 10.59a1.002 1.002 0 0 0 0-1.42Z"/></svg></label></starlight-theme-select><script>
40
+ StarlightThemeProvider.updatePickers();
41
+ </script><script type="module">var e=`starlight-theme`,t=e=>e===`auto`||e===`dark`||e===`light`?e:`auto`,n=()=>t(typeof localStorage<`u`&&localStorage.getItem(e));function r(t){typeof localStorage<`u`&&localStorage.setItem(e,t===`light`||t===`dark`?t:``)}var i=()=>matchMedia(`(prefers-color-scheme: light)`).matches?`light`:`dark`;function a(e){StarlightThemeProvider.updatePickers(e),document.documentElement.dataset.theme=e===`auto`?i():e,r(e)}matchMedia(`(prefers-color-scheme: light)`).addEventListener(`change`,()=>{n()===`auto`&&a(`auto`)});var o=class extends HTMLElement{constructor(){super(),a(n()),this.querySelector(`select`)?.addEventListener(`change`,e=>{e.currentTarget instanceof HTMLSelectElement&&a(t(e.currentTarget.value))})}};customElements.define(`starlight-theme-select`,o);</script><script type="module">var e=class extends HTMLElement{constructor(){super();let e=this.querySelector(`select`);e&&(e.addEventListener(`change`,e=>{e.currentTarget instanceof HTMLSelectElement&&(window.location.pathname=e.currentTarget.value)}),window.addEventListener(`pageshow`,t=>{if(!t.persisted)return;let n=e.querySelector(`option[selected]`)?.index;n!==e.selectedIndex&&(e.selectedIndex=n??0)}))}};customElements.define(`starlight-lang-select`,e);</script></div></div></header><nav class="sidebar print:hidden astro-5auw3bkx" aria-label="Main"><starlight-menu-button class="print:hidden astro-gnkm6dw5"><button aria-expanded="false" aria-label="Menu" aria-controls="starlight__sidebar" class="sl-flex md:sl-hidden astro-gnkm6dw5"><svg aria-hidden="true" class="open-menu astro-gnkm6dw5 astro-24n5ceuf" width="16" height="16" viewBox="0 0 24 24" fill="currentColor" style="--sl-icon-size: 1em;"><path d="M3 8h18a1 1 0 1 0 0-2H3a1 1 0 0 0 0 2Zm18 8H3a1 1 0 0 0 0 2h18a1 1 0 0 0 0-2Zm0-5H3a1 1 0 0 0 0 2h18a1 1 0 0 0 0-2Z"/></svg><svg aria-hidden="true" class="close-menu astro-gnkm6dw5 astro-24n5ceuf" width="16" height="16" viewBox="0 0 24 24" fill="currentColor" style="--sl-icon-size: 1em;"><path d="m13.41 12 6.3-6.29a1.004 1.004 0 1 0-1.42-1.42L12 10.59l-6.29-6.3a1.004 1.004 0 0 0-1.42 1.42l6.3 6.29-6.3 6.29a1 1 0 0 0 0 1.42.998.998 0 0 0 1.42 0l6.29-6.3 6.29 6.3a.999.999 0 0 0 1.42 0 1 1 0 0 0 0-1.42L13.41 12Z"/></svg></button></starlight-menu-button><script type="module">var e=class extends HTMLElement{btn=this.querySelector(`button`);constructor(){super(),this.btn.addEventListener(`click`,()=>this.toggleExpanded());let e=this.closest(`nav`);e&&e.addEventListener(`keyup`,e=>this.closeOnEscape(e)),matchMedia(`(min-width: 50em)`).addEventListener(`change`,()=>this.setExpanded(!1))}setExpanded(e){this.setAttribute(`aria-expanded`,String(e)),document.body.toggleAttribute(`data-mobile-menu-expanded`,e),document.querySelectorAll(`.main-frame, .sl-skip-link`).forEach(t=>{t.toggleAttribute(`inert`,e)})}toggleExpanded(){this.setExpanded(this.getAttribute(`aria-expanded`)!==`true`)}closeOnEscape(e){e.code===`Escape`&&(this.setExpanded(!1),this.btn.focus())}};customElements.define(`starlight-menu-button`,e);</script><div id="starlight__sidebar" class="sidebar-pane astro-5auw3bkx"><div class="sidebar-content sl-flex astro-5auw3bkx"><sl-sidebar-state-persist data-hash="03jp7ll" class="astro-pcs3l5kp"><script aria-hidden="true">
42
+ (() => {
43
+ try {
44
+ if (!matchMedia('(min-width: 50em)').matches) return;
45
+ /** @type {HTMLElement | null} */
46
+ const target = document.querySelector('sl-sidebar-state-persist');
47
+ const state = JSON.parse(sessionStorage.getItem('sl-sidebar-state') || '0');
48
+ if (!target || !state || target.dataset.hash !== state.hash) return;
49
+ window._starlightScrollRestore = state.scroll;
50
+ customElements.define(
51
+ 'sl-sidebar-restore',
52
+ class SidebarRestore extends HTMLElement {
53
+ connectedCallback() {
54
+ try {
55
+ const idx = parseInt(this.dataset.index || '');
56
+ const details = this.closest('details');
57
+ if (details && typeof state.open[idx] === 'boolean') details.open = state.open[idx];
58
+ } catch {}
59
+ }
60
+ }
61
+ );
62
+ } catch {}
63
+ })();
64
+ </script><ul class="top-level astro-ck7l566j"><li class="astro-ck7l566j"><details open class="astro-ck7l566j"><summary class="astro-ck7l566j"><span class="group-label astro-ck7l566j"><span class="large astro-ck7l566j">Using Velora</span></span><svg aria-hidden="true" class="caret astro-ck7l566j astro-24n5ceuf" width="16" height="16" viewBox="0 0 24 24" fill="currentColor" style="--sl-icon-size: 1.25rem;"><path d="m14.83 11.29-4.24-4.24a1 1 0 1 0-1.42 1.41L12.71 12l-3.54 3.54a1 1 0 0 0 0 1.41 1 1 0 0 0 .71.29 1 1 0 0 0 .71-.29l4.24-4.24a1.002 1.002 0 0 0 0-1.42Z"/></svg></summary><sl-sidebar-restore data-index="0"></sl-sidebar-restore><ul class="astro-ck7l566j"><li class="astro-ck7l566j"><a href="/docs/v1/using-velora/" class="astro-ck7l566j"><span class="astro-ck7l566j">Using Velora</span></a></li><li class="astro-ck7l566j"><a href="/docs/v1/using-velora/getting-started/" class="astro-ck7l566j"><span class="astro-ck7l566j">Getting started</span></a></li><li class="astro-ck7l566j"><a href="/docs/v1/using-velora/installation/" class="astro-ck7l566j"><span class="astro-ck7l566j">Installation</span></a></li><li class="astro-ck7l566j"><a href="/docs/v1/using-velora/content-model/" class="astro-ck7l566j"><span class="astro-ck7l566j">Document types</span></a></li><li class="astro-ck7l566j"><a href="/docs/v1/using-velora/editing/" class="astro-ck7l566j"><span class="astro-ck7l566j">Content editing</span></a></li><li class="astro-ck7l566j"><a href="/docs/v1/using-velora/media/" class="astro-ck7l566j"><span class="astro-ck7l566j">Media</span></a></li><li class="astro-ck7l566j"><a href="/docs/v1/using-velora/users-and-roles/" class="astro-ck7l566j"><span class="astro-ck7l566j">Users and roles</span></a></li><li class="astro-ck7l566j"><a href="/docs/v1/using-velora/installing-plugins/" class="astro-ck7l566j"><span class="astro-ck7l566j">Installing plugins</span></a></li><li class="astro-ck7l566j"><a href="/docs/v1/using-velora/headless-api/" aria-current="page" class="astro-ck7l566j"><span class="astro-ck7l566j">Headless API</span></a></li><li class="astro-ck7l566j"><a href="/docs/v1/using-velora/build-a-headless-frontend/" class="astro-ck7l566j"><span class="astro-ck7l566j">Build a headless frontend</span></a></li></ul></details></li></ul><script aria-hidden="true">
65
+ (() => {
66
+ const scroller = document.getElementById('starlight__sidebar');
67
+ if (!window._starlightScrollRestore || !scroller) return;
68
+ scroller.scrollTop = window._starlightScrollRestore;
69
+ delete window._starlightScrollRestore;
70
+ })();
71
+ </script></sl-sidebar-state-persist><div class="md:sl-hidden"><div class="mobile-preferences sl-flex astro-yfet6llj"><div class="social-icons astro-yfet6llj"></div><starlight-theme-select><label style="--sl-select-width: 6.25em" class="astro-7uz3f5wr"><span class="sr-only astro-7uz3f5wr">Select theme</span><svg aria-hidden="true" class="icon label-icon astro-7uz3f5wr astro-24n5ceuf" width="16" height="16" viewBox="0 0 24 24" fill="currentColor" style="--sl-icon-size: 1em;"><path d="M21 14h-1V7a3 3 0 0 0-3-3H7a3 3 0 0 0-3 3v7H3a1 1 0 0 0-1 1v2a3 3 0 0 0 3 3h14a3 3 0 0 0 3-3v-2a1 1 0 0 0-1-1ZM6 7a1 1 0 0 1 1-1h10a1 1 0 0 1 1 1v7H6V7Zm14 10a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1v-1h16v1Z"/></svg><select autocomplete="off" class="astro-7uz3f5wr"><option value="dark" class="astro-7uz3f5wr">Dark</option><option value="light" class="astro-7uz3f5wr">Light</option><option value="auto" selected class="astro-7uz3f5wr">Auto</option></select><svg aria-hidden="true" class="icon caret astro-7uz3f5wr astro-24n5ceuf" width="16" height="16" viewBox="0 0 24 24" fill="currentColor" style="--sl-icon-size: 1em;"><path d="M17 9.17a1 1 0 0 0-1.41 0L12 12.71 8.46 9.17a1 1 0 1 0-1.41 1.42l4.24 4.24a1.002 1.002 0 0 0 1.42 0L17 10.59a1.002 1.002 0 0 0 0-1.42Z"/></svg></label></starlight-theme-select><script>
72
+ StarlightThemeProvider.updatePickers();
73
+ </script></div></div></div></div></nav><div class="main-frame astro-5auw3bkx"><script type="module">var e=document.getElementById(`starlight__sidebar`),t=e?.querySelector(`sl-sidebar-state-persist`),n=`sl-sidebar-state`,r=()=>{let r=[],i=t?.dataset.hash||``;try{let e=sessionStorage.getItem(n),t=JSON.parse(e||`{}`);Array.isArray(t.open)&&t.hash===i&&(r=t.open)}catch{}return{hash:i,open:r,scroll:e?.scrollTop||0}},i=e=>{try{sessionStorage.setItem(n,JSON.stringify(e))}catch{}},a=()=>i(r()),o=(e,t)=>{let n=r();n.open[t]=e,i(n)};t?.addEventListener(`click`,e=>{if(!(e.target instanceof Element))return;let t=e.target.closest(`summary`)?.closest(`details`);if(!t)return;let n=t.querySelector(`sl-sidebar-restore`),r=parseInt(n?.dataset.index||``);isNaN(r)||o(!t.open,r)}),addEventListener(`visibilitychange`,()=>{document.visibilityState===`hidden`&&a()});</script><div class="lg:sl-flex astro-perbdhd2"><aside class="right-sidebar-container print:hidden astro-perbdhd2"><div class="right-sidebar astro-perbdhd2"><div class="lg:sl-hidden astro-epm5tkrc"><mobile-starlight-toc data-min-h="2" data-max-h="3" class="astro-bnvebtr4"><nav aria-labelledby="starlight__on-this-page--mobile" class="astro-bnvebtr4"><details id="starlight__mobile-toc" class="astro-bnvebtr4"><summary id="starlight__on-this-page--mobile" class="sl-flex astro-bnvebtr4"><span class="toggle sl-flex astro-bnvebtr4">On this page<svg aria-hidden="true" class="caret astro-bnvebtr4 astro-24n5ceuf" width="16" height="16" viewBox="0 0 24 24" fill="currentColor" style="--sl-icon-size: 1rem;"><path d="m14.83 11.29-4.24-4.24a1 1 0 1 0-1.42 1.41L12.71 12l-3.54 3.54a1 1 0 0 0 0 1.41 1 1 0 0 0 .71.29 1 1 0 0 0 .71-.29l4.24-4.24a1.002 1.002 0 0 0 0-1.42Z"/></svg></span><span class="display-current astro-bnvebtr4"></span></summary><div class="dropdown astro-bnvebtr4"><ul class="isMobile astro-lptfoybu" style="--depth: 0;"><li style="--depth: 0;" class="astro-lptfoybu"><a href="#_top" style="--depth: 0;" class="astro-lptfoybu"><span style="--depth: 0;" class="astro-lptfoybu">Overview</span></a></li><li style="--depth: 0;" class="astro-lptfoybu"><a href="#turn-it-on" style="--depth: 0;" class="astro-lptfoybu"><span style="--depth: 0;" class="astro-lptfoybu">Turn it on</span></a></li><li style="--depth: 0;" class="astro-lptfoybu"><a href="#authentication" style="--depth: 0;" class="astro-lptfoybu"><span style="--depth: 0;" class="astro-lptfoybu">Authentication</span></a><ul class="isMobile astro-lptfoybu" style="--depth: 1;"><li style="--depth: 1;" class="astro-lptfoybu"><a href="#what-a-key-can-do" style="--depth: 1;" class="astro-lptfoybu"><span style="--depth: 1;" class="astro-lptfoybu">What a key can do</span></a></li><li style="--depth: 1;" class="astro-lptfoybu"><a href="#the-public-demo" style="--depth: 1;" class="astro-lptfoybu"><span style="--depth: 1;" class="astro-lptfoybu">The public demo</span></a></li></ul></li><li style="--depth: 0;" class="astro-lptfoybu"><a href="#conventions" style="--depth: 0;" class="astro-lptfoybu"><span style="--depth: 0;" class="astro-lptfoybu">Conventions</span></a><ul class="isMobile astro-lptfoybu" style="--depth: 1;"><li style="--depth: 1;" class="astro-lptfoybu"><a href="#cursor-pagination" style="--depth: 1;" class="astro-lptfoybu"><span style="--depth: 1;" class="astro-lptfoybu">Cursor pagination</span></a></li><li style="--depth: 1;" class="astro-lptfoybu"><a href="#published-only-reads" style="--depth: 1;" class="astro-lptfoybu"><span style="--depth: 1;" class="astro-lptfoybu">Published-only reads</span></a></li><li style="--depth: 1;" class="astro-lptfoybu"><a href="#locale-and-fallback" style="--depth: 1;" class="astro-lptfoybu"><span style="--depth: 1;" class="astro-lptfoybu">Locale and fallback</span></a></li><li style="--depth: 1;" class="astro-lptfoybu"><a href="#preview-tokens" style="--depth: 1;" class="astro-lptfoybu"><span style="--depth: 1;" class="astro-lptfoybu">Preview tokens</span></a></li></ul></li><li style="--depth: 0;" class="astro-lptfoybu"><a href="#content" style="--depth: 0;" class="astro-lptfoybu"><span style="--depth: 0;" class="astro-lptfoybu">Content</span></a><ul class="isMobile astro-lptfoybu" style="--depth: 1;"><li style="--depth: 1;" class="astro-lptfoybu"><a href="#get-apiv1content--search-and-filter" style="--depth: 1;" class="astro-lptfoybu"><span style="--depth: 1;" class="astro-lptfoybu">GET /api/v1/content — search and filter</span></a></li><li style="--depth: 1;" class="astro-lptfoybu"><a href="#get-apiv1contentroots--top-level-nodes" style="--depth: 1;" class="astro-lptfoybu"><span style="--depth: 1;" class="astro-lptfoybu">GET /api/v1/content/roots — top-level nodes</span></a></li><li style="--depth: 1;" class="astro-lptfoybu"><a href="#get-apiv1contentidchildren--one-level-down" style="--depth: 1;" class="astro-lptfoybu"><span style="--depth: 1;" class="astro-lptfoybu">GET /api/v1/content/:id/children — one level down</span></a></li><li style="--depth: 1;" class="astro-lptfoybu"><a href="#get-apiv1contentid--one-item-with-its-fields" style="--depth: 1;" class="astro-lptfoybu"><span style="--depth: 1;" class="astro-lptfoybu">GET /api/v1/content/:id — one item with its fields</span></a></li><li style="--depth: 1;" class="astro-lptfoybu"><a href="#get-apiv1contentby-path--resolve-a-url" style="--depth: 1;" class="astro-lptfoybu"><span style="--depth: 1;" class="astro-lptfoybu">GET /api/v1/content/by-path — resolve a URL</span></a></li></ul></li><li style="--depth: 0;" class="astro-lptfoybu"><a href="#writing-content" style="--depth: 0;" class="astro-lptfoybu"><span style="--depth: 0;" class="astro-lptfoybu">Writing content</span></a><ul class="isMobile astro-lptfoybu" style="--depth: 1;"><li style="--depth: 1;" class="astro-lptfoybu"><a href="#post-apiv1content--create-a-node" style="--depth: 1;" class="astro-lptfoybu"><span style="--depth: 1;" class="astro-lptfoybu">POST /api/v1/content — create a node</span></a></li><li style="--depth: 1;" class="astro-lptfoybu"><a href="#put-apiv1contentid--replace-all-fields" style="--depth: 1;" class="astro-lptfoybu"><span style="--depth: 1;" class="astro-lptfoybu">PUT /api/v1/content/:id — replace all fields</span></a></li><li style="--depth: 1;" class="astro-lptfoybu"><a href="#patch-apiv1contentid--change-some-fields" style="--depth: 1;" class="astro-lptfoybu"><span style="--depth: 1;" class="astro-lptfoybu">PATCH /api/v1/content/:id — change some fields</span></a></li><li style="--depth: 1;" class="astro-lptfoybu"><a href="#post-apiv1contentidpublish--make-it-readable" style="--depth: 1;" class="astro-lptfoybu"><span style="--depth: 1;" class="astro-lptfoybu">POST /api/v1/content/:id/publish — make it readable</span></a></li></ul></li><li style="--depth: 0;" class="astro-lptfoybu"><a href="#document-types" style="--depth: 0;" class="astro-lptfoybu"><span style="--depth: 0;" class="astro-lptfoybu">Document types</span></a><ul class="isMobile astro-lptfoybu" style="--depth: 1;"><li style="--depth: 1;" class="astro-lptfoybu"><a href="#get-apiv1document-types" style="--depth: 1;" class="astro-lptfoybu"><span style="--depth: 1;" class="astro-lptfoybu">GET /api/v1/document-types</span></a></li><li style="--depth: 1;" class="astro-lptfoybu"><a href="#get-apiv1document-typesid" style="--depth: 1;" class="astro-lptfoybu"><span style="--depth: 1;" class="astro-lptfoybu">GET /api/v1/document-types/:id</span></a></li></ul></li><li style="--depth: 0;" class="astro-lptfoybu"><a href="#media" style="--depth: 0;" class="astro-lptfoybu"><span style="--depth: 0;" class="astro-lptfoybu">Media</span></a><ul class="isMobile astro-lptfoybu" style="--depth: 1;"><li style="--depth: 1;" class="astro-lptfoybu"><a href="#get-apiv1media" style="--depth: 1;" class="astro-lptfoybu"><span style="--depth: 1;" class="astro-lptfoybu">GET /api/v1/media</span></a></li><li style="--depth: 1;" class="astro-lptfoybu"><a href="#get-apiv1mediaid" style="--depth: 1;" class="astro-lptfoybu"><span style="--depth: 1;" class="astro-lptfoybu">GET /api/v1/media/:id</span></a></li></ul></li><li style="--depth: 0;" class="astro-lptfoybu"><a href="#templates" style="--depth: 0;" class="astro-lptfoybu"><span style="--depth: 0;" class="astro-lptfoybu">Templates</span></a><ul class="isMobile astro-lptfoybu" style="--depth: 1;"><li style="--depth: 1;" class="astro-lptfoybu"><a href="#get-apiv1templates" style="--depth: 1;" class="astro-lptfoybu"><span style="--depth: 1;" class="astro-lptfoybu">GET /api/v1/templates</span></a></li></ul></li><li style="--depth: 0;" class="astro-lptfoybu"><a href="#graphql" style="--depth: 0;" class="astro-lptfoybu"><span style="--depth: 0;" class="astro-lptfoybu">GraphQL</span></a><ul class="isMobile astro-lptfoybu" style="--depth: 1;"><li style="--depth: 1;" class="astro-lptfoybu"><a href="#graphiql" style="--depth: 1;" class="astro-lptfoybu"><span style="--depth: 1;" class="astro-lptfoybu">GraphiQL</span></a></li></ul></li><li style="--depth: 0;" class="astro-lptfoybu"><a href="#openapi" style="--depth: 0;" class="astro-lptfoybu"><span style="--depth: 0;" class="astro-lptfoybu">OpenAPI</span></a></li><li style="--depth: 0;" class="astro-lptfoybu"><a href="#errors" style="--depth: 0;" class="astro-lptfoybu"><span style="--depth: 0;" class="astro-lptfoybu">Errors</span></a></li><li style="--depth: 0;" class="astro-lptfoybu"><a href="#rate-limits" style="--depth: 0;" class="astro-lptfoybu"><span style="--depth: 0;" class="astro-lptfoybu">Rate limits</span></a></li><li style="--depth: 0;" class="astro-lptfoybu"><a href="#next" style="--depth: 0;" class="astro-lptfoybu"><span style="--depth: 0;" class="astro-lptfoybu">Next</span></a></li></ul></div></details></nav></mobile-starlight-toc><script type="module" src="/docs/_astro/MobileTableOfContents.astro_astro_type_script_index_0_lang.BcSo_yiZ.js"></script></div><div class="right-sidebar-panel sl-hidden lg:sl-block astro-epm5tkrc"><div class="sl-container astro-epm5tkrc"><starlight-toc data-min-h="2" data-max-h="3"><nav aria-labelledby="starlight__on-this-page"><h2 id="starlight__on-this-page">On this page</h2><ul class="astro-lptfoybu" style="--depth: 0;"><li style="--depth: 0;" class="astro-lptfoybu"><a href="#_top" style="--depth: 0;" class="astro-lptfoybu"><span style="--depth: 0;" class="astro-lptfoybu">Overview</span></a></li><li style="--depth: 0;" class="astro-lptfoybu"><a href="#turn-it-on" style="--depth: 0;" class="astro-lptfoybu"><span style="--depth: 0;" class="astro-lptfoybu">Turn it on</span></a></li><li style="--depth: 0;" class="astro-lptfoybu"><a href="#authentication" style="--depth: 0;" class="astro-lptfoybu"><span style="--depth: 0;" class="astro-lptfoybu">Authentication</span></a><ul class="astro-lptfoybu" style="--depth: 1;"><li style="--depth: 1;" class="astro-lptfoybu"><a href="#what-a-key-can-do" style="--depth: 1;" class="astro-lptfoybu"><span style="--depth: 1;" class="astro-lptfoybu">What a key can do</span></a></li><li style="--depth: 1;" class="astro-lptfoybu"><a href="#the-public-demo" style="--depth: 1;" class="astro-lptfoybu"><span style="--depth: 1;" class="astro-lptfoybu">The public demo</span></a></li></ul></li><li style="--depth: 0;" class="astro-lptfoybu"><a href="#conventions" style="--depth: 0;" class="astro-lptfoybu"><span style="--depth: 0;" class="astro-lptfoybu">Conventions</span></a><ul class="astro-lptfoybu" style="--depth: 1;"><li style="--depth: 1;" class="astro-lptfoybu"><a href="#cursor-pagination" style="--depth: 1;" class="astro-lptfoybu"><span style="--depth: 1;" class="astro-lptfoybu">Cursor pagination</span></a></li><li style="--depth: 1;" class="astro-lptfoybu"><a href="#published-only-reads" style="--depth: 1;" class="astro-lptfoybu"><span style="--depth: 1;" class="astro-lptfoybu">Published-only reads</span></a></li><li style="--depth: 1;" class="astro-lptfoybu"><a href="#locale-and-fallback" style="--depth: 1;" class="astro-lptfoybu"><span style="--depth: 1;" class="astro-lptfoybu">Locale and fallback</span></a></li><li style="--depth: 1;" class="astro-lptfoybu"><a href="#preview-tokens" style="--depth: 1;" class="astro-lptfoybu"><span style="--depth: 1;" class="astro-lptfoybu">Preview tokens</span></a></li></ul></li><li style="--depth: 0;" class="astro-lptfoybu"><a href="#content" style="--depth: 0;" class="astro-lptfoybu"><span style="--depth: 0;" class="astro-lptfoybu">Content</span></a><ul class="astro-lptfoybu" style="--depth: 1;"><li style="--depth: 1;" class="astro-lptfoybu"><a href="#get-apiv1content--search-and-filter" style="--depth: 1;" class="astro-lptfoybu"><span style="--depth: 1;" class="astro-lptfoybu">GET /api/v1/content — search and filter</span></a></li><li style="--depth: 1;" class="astro-lptfoybu"><a href="#get-apiv1contentroots--top-level-nodes" style="--depth: 1;" class="astro-lptfoybu"><span style="--depth: 1;" class="astro-lptfoybu">GET /api/v1/content/roots — top-level nodes</span></a></li><li style="--depth: 1;" class="astro-lptfoybu"><a href="#get-apiv1contentidchildren--one-level-down" style="--depth: 1;" class="astro-lptfoybu"><span style="--depth: 1;" class="astro-lptfoybu">GET /api/v1/content/:id/children — one level down</span></a></li><li style="--depth: 1;" class="astro-lptfoybu"><a href="#get-apiv1contentid--one-item-with-its-fields" style="--depth: 1;" class="astro-lptfoybu"><span style="--depth: 1;" class="astro-lptfoybu">GET /api/v1/content/:id — one item with its fields</span></a></li><li style="--depth: 1;" class="astro-lptfoybu"><a href="#get-apiv1contentby-path--resolve-a-url" style="--depth: 1;" class="astro-lptfoybu"><span style="--depth: 1;" class="astro-lptfoybu">GET /api/v1/content/by-path — resolve a URL</span></a></li></ul></li><li style="--depth: 0;" class="astro-lptfoybu"><a href="#writing-content" style="--depth: 0;" class="astro-lptfoybu"><span style="--depth: 0;" class="astro-lptfoybu">Writing content</span></a><ul class="astro-lptfoybu" style="--depth: 1;"><li style="--depth: 1;" class="astro-lptfoybu"><a href="#post-apiv1content--create-a-node" style="--depth: 1;" class="astro-lptfoybu"><span style="--depth: 1;" class="astro-lptfoybu">POST /api/v1/content — create a node</span></a></li><li style="--depth: 1;" class="astro-lptfoybu"><a href="#put-apiv1contentid--replace-all-fields" style="--depth: 1;" class="astro-lptfoybu"><span style="--depth: 1;" class="astro-lptfoybu">PUT /api/v1/content/:id — replace all fields</span></a></li><li style="--depth: 1;" class="astro-lptfoybu"><a href="#patch-apiv1contentid--change-some-fields" style="--depth: 1;" class="astro-lptfoybu"><span style="--depth: 1;" class="astro-lptfoybu">PATCH /api/v1/content/:id — change some fields</span></a></li><li style="--depth: 1;" class="astro-lptfoybu"><a href="#post-apiv1contentidpublish--make-it-readable" style="--depth: 1;" class="astro-lptfoybu"><span style="--depth: 1;" class="astro-lptfoybu">POST /api/v1/content/:id/publish — make it readable</span></a></li></ul></li><li style="--depth: 0;" class="astro-lptfoybu"><a href="#document-types" style="--depth: 0;" class="astro-lptfoybu"><span style="--depth: 0;" class="astro-lptfoybu">Document types</span></a><ul class="astro-lptfoybu" style="--depth: 1;"><li style="--depth: 1;" class="astro-lptfoybu"><a href="#get-apiv1document-types" style="--depth: 1;" class="astro-lptfoybu"><span style="--depth: 1;" class="astro-lptfoybu">GET /api/v1/document-types</span></a></li><li style="--depth: 1;" class="astro-lptfoybu"><a href="#get-apiv1document-typesid" style="--depth: 1;" class="astro-lptfoybu"><span style="--depth: 1;" class="astro-lptfoybu">GET /api/v1/document-types/:id</span></a></li></ul></li><li style="--depth: 0;" class="astro-lptfoybu"><a href="#media" style="--depth: 0;" class="astro-lptfoybu"><span style="--depth: 0;" class="astro-lptfoybu">Media</span></a><ul class="astro-lptfoybu" style="--depth: 1;"><li style="--depth: 1;" class="astro-lptfoybu"><a href="#get-apiv1media" style="--depth: 1;" class="astro-lptfoybu"><span style="--depth: 1;" class="astro-lptfoybu">GET /api/v1/media</span></a></li><li style="--depth: 1;" class="astro-lptfoybu"><a href="#get-apiv1mediaid" style="--depth: 1;" class="astro-lptfoybu"><span style="--depth: 1;" class="astro-lptfoybu">GET /api/v1/media/:id</span></a></li></ul></li><li style="--depth: 0;" class="astro-lptfoybu"><a href="#templates" style="--depth: 0;" class="astro-lptfoybu"><span style="--depth: 0;" class="astro-lptfoybu">Templates</span></a><ul class="astro-lptfoybu" style="--depth: 1;"><li style="--depth: 1;" class="astro-lptfoybu"><a href="#get-apiv1templates" style="--depth: 1;" class="astro-lptfoybu"><span style="--depth: 1;" class="astro-lptfoybu">GET /api/v1/templates</span></a></li></ul></li><li style="--depth: 0;" class="astro-lptfoybu"><a href="#graphql" style="--depth: 0;" class="astro-lptfoybu"><span style="--depth: 0;" class="astro-lptfoybu">GraphQL</span></a><ul class="astro-lptfoybu" style="--depth: 1;"><li style="--depth: 1;" class="astro-lptfoybu"><a href="#graphiql" style="--depth: 1;" class="astro-lptfoybu"><span style="--depth: 1;" class="astro-lptfoybu">GraphiQL</span></a></li></ul></li><li style="--depth: 0;" class="astro-lptfoybu"><a href="#openapi" style="--depth: 0;" class="astro-lptfoybu"><span style="--depth: 0;" class="astro-lptfoybu">OpenAPI</span></a></li><li style="--depth: 0;" class="astro-lptfoybu"><a href="#errors" style="--depth: 0;" class="astro-lptfoybu"><span style="--depth: 0;" class="astro-lptfoybu">Errors</span></a></li><li style="--depth: 0;" class="astro-lptfoybu"><a href="#rate-limits" style="--depth: 0;" class="astro-lptfoybu"><span style="--depth: 0;" class="astro-lptfoybu">Rate limits</span></a></li><li style="--depth: 0;" class="astro-lptfoybu"><a href="#next" style="--depth: 0;" class="astro-lptfoybu"><span style="--depth: 0;" class="astro-lptfoybu">Next</span></a></li></ul></nav></starlight-toc><script type="module" src="/docs/_astro/TableOfContents.astro_astro_type_script_index_0_lang.Csloo1VZ.js"></script></div></div></div></aside><div class="main-pane astro-perbdhd2"><main data-pagefind-body class="astro-ewhz5xxr" lang="en" dir="ltr"><div class="content-panel astro-ahkzlqop"><div class="sl-container astro-ahkzlqop"><h1 id="_top" class="astro-oef2efwn">Headless API</h1></div></div><div class="content-panel astro-ahkzlqop"><div class="sl-container astro-ahkzlqop"><div class="sl-markdown-content"><p><code dir="auto">/api/v1</code> is Velora’s public content API: the surface your own frontend
74
+ reads from, and — with a key minted for writing — writes to. It is a
75
+ deploy-time opt-in. When it is off, none of the routes below exist at all
76
+ (a <code dir="auto">404</code>, not a <code dir="auto">403</code>), which is what lets one codebase run as a closed
77
+ back office or as a headless content service depending on how it is
78
+ deployed.</p>
79
+ <p>This page is the complete reference. Everything on it was read out of the
80
+ route definitions in <code dir="auto">apps/server/src/routes/public-api.ts</code> and the Zod
81
+ schemas in <code dir="auto">packages/api-schemas</code>; the JSON blocks marked <strong>live</strong> are
82
+ verbatim responses from the public demo at <code dir="auto">https://demo.velora-cms.com</code>,
83
+ and the rest are shapes derived from those schemas, where ids, timestamps
84
+ and field names are illustrative.</p>
85
+ <div class="sl-heading-wrapper level-h2"><h2 id="turn-it-on">Turn it on</h2><a class="sl-anchor-link" href="#turn-it-on"><span aria-hidden="true" class="sl-anchor-icon"><svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor"><path d="m12.11 15.39-3.88 3.88a2.52 2.52 0 0 1-3.5 0 2.47 2.47 0 0 1 0-3.5l3.88-3.88a1 1 0 1 0-1.42-1.42l-3.88 3.89a4.48 4.48 0 0 0 6.33 6.33l3.89-3.88a1 1 0 0 0-1.42-1.42m8.58-12.08a4.49 4.49 0 0 0-6.33 0l-3.89 3.88a1 1 0 1 0 1.42 1.42l3.88-3.88a2.52 2.52 0 0 1 3.5 0 2.47 2.47 0 0 1 0 3.5l-3.88 3.88a1 1 0 0 0 0 1.42 1 1 0 0 0 1.42 0l3.88-3.89a4.49 4.49 0 0 0 0-6.33M8.83 15.17a1 1 0 0 0 .71.29 1 1 0 0 0 .71-.29l4.92-4.92a1 1 0 1 0-1.42-1.42l-4.92 4.92a1 1 0 0 0 0 1.42"></path></svg></span><span class="sr-only" data-pagefind-ignore="">Section titled “Turn it on”</span></a></div>
86
+ <p>The public API is registered at boot when <code dir="auto">VELORA_HEADLESS</code> is <code dir="auto">true</code> or
87
+ <code dir="auto">1</code>. Anything else — including unset — leaves the namespace unregistered.
88
+ There is no runtime toggle in the admin, deliberately: this is a property
89
+ of the deployment, like the database dialect.</p>
90
+
91
+
92
+
93
+
94
+
95
+
96
+
97
+
98
+
99
+
100
+
101
+
102
+
103
+
104
+
105
+
106
+
107
+
108
+
109
+
110
+
111
+
112
+
113
+
114
+
115
+
116
+
117
+
118
+
119
+
120
+
121
+
122
+
123
+
124
+
125
+
126
+
127
+
128
+
129
+
130
+
131
+
132
+
133
+
134
+
135
+
136
+
137
+
138
+
139
+
140
+
141
+
142
+
143
+
144
+
145
+
146
+
147
+
148
+
149
+
150
+
151
+
152
+
153
+
154
+
155
+ <table><thead><tr><th>Name</th><th>Type</th><th>Meaning</th><th>Example</th></tr></thead><tbody><tr><td><code dir="auto">VELORA_HEADLESS</code></td><td><code dir="auto">"true"</code> or <code dir="auto">"1"</code> to enable</td><td>Registers the whole <code dir="auto">/api/v1</code> namespace, GraphQL included. Unset or any other value = the routes do not exist. No default (off).</td><td><code dir="auto">true</code></td></tr><tr><td><code dir="auto">VELORA_PUBLIC_DOCS</code></td><td>boolean-ish string</td><td>Serves the public OpenAPI UI at <code dir="auto">/api/v1/docs</code> and the GraphiQL page at <code dir="auto">/api/v1/graphiql</code>, and allows GraphQL introspection. <strong>On by default</strong> — only <code dir="auto">false</code> or <code dir="auto">0</code> turns it off, which <code dir="auto">404</code>s the docs without touching the API.</td><td><code dir="auto">false</code></td></tr><tr><td><code dir="auto">VELORA_GRAPHQL_MAX_DEPTH</code></td><td>positive integer</td><td>Maximum nesting depth of a GraphQL query. Default <code dir="auto">8</code>. Non-integer, zero and negative values fall back to the default.</td><td><code dir="auto">8</code></td></tr><tr><td><code dir="auto">VELORA_GRAPHQL_MAX_COMPLEXITY</code></td><td>positive integer</td><td>Maximum number of field selections a GraphQL operation may reach, fragments expanded per spread site. Default <code dir="auto">500</code>.</td><td><code dir="auto">500</code></td></tr><tr><td><code dir="auto">VELORA_RATE_LIMIT_MAX</code></td><td>positive integer</td><td>Requests allowed per window for a caller presenting a real, unrevoked API key. Default <code dir="auto">100</code>.</td><td><code dir="auto">100</code></td></tr><tr><td><code dir="auto">VELORA_RATE_LIMIT_IP_MAX</code></td><td>positive integer</td><td>Requests allowed per window for an anonymous caller — no key, or a key that does not resolve. Default <code dir="auto">60</code>.</td><td><code dir="auto">60</code></td></tr><tr><td><code dir="auto">VELORA_RATE_LIMIT_WINDOW</code></td><td>duration string</td><td>The window both ceilings are measured over. Default <code dir="auto">1 minute</code>. An unparseable value falls back to the default rather than breaking the routes.</td><td><code dir="auto">1 minute</code></td></tr><tr><td><code dir="auto">VELORA_PREVIEW_TOKEN_TTL</code></td><td>positive integer (seconds)</td><td>Lifetime of a preview token minted for a draft read. Default <code dir="auto">1800</code> (30 minutes).</td><td><code dir="auto">900</code></td></tr><tr><td><code dir="auto">JWT_SECRET</code></td><td>string</td><td>Already required for admin sessions; a separate key is derived from it to sign preview tokens. Rotating it invalidates outstanding preview tokens.</td><td><code dir="auto">a-long-random-string</code></td></tr></tbody></table>
156
+ <p>A minimal headless deployment sets one variable beyond the usual core
157
+ configuration. Run this from the repository root:</p>
158
+ <div class="expressive-code"><link rel="stylesheet" href="/docs/_astro/ec.w36nc.css"><script type="module" src="/docs/_astro/ec.0vx5m.js"></script><figure class="frame is-terminal not-content"><figcaption class="header"><span class="title"></span><span class="sr-only">Terminal window</span></figcaption><pre data-language="sh"><code><div class="ec-line"><div class="code"><span style="--0:#C5E478;--1:#3B61B0">VELORA_HEADLESS</span><span style="--0:#C792EA;--1:#8844AE">=</span><span style="--0:#ECC48D;--1:#3B61B0">true</span><span style="--0:#D6DEEB;--1:#403F53"> </span><span style="--0:#82AAFF;--1:#3B61B0">node</span><span style="--0:#D6DEEB;--1:#403F53"> </span><span style="--0:#ECC48D;--1:#3B61B0">apps/server/dist/index.js</span></div></div></code></pre><div class="copy"><div aria-live="polite"></div><button title="Copy to clipboard" data-copied="Copied!" data-code="VELORA_HEADLESS=true node apps/server/dist/index.js"><div></div></button></div></figure></div>
159
+ <p>The server boots as normal and the <code dir="auto">/api/v1</code> routes start answering; with
160
+ the variable removed, the same requests return Velora’s ordinary 404 body
161
+ instead.</p>
162
+ <div class="sl-heading-wrapper level-h2"><h2 id="authentication">Authentication</h2><a class="sl-anchor-link" href="#authentication"><span aria-hidden="true" class="sl-anchor-icon"><svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor"><path d="m12.11 15.39-3.88 3.88a2.52 2.52 0 0 1-3.5 0 2.47 2.47 0 0 1 0-3.5l3.88-3.88a1 1 0 1 0-1.42-1.42l-3.88 3.89a4.48 4.48 0 0 0 6.33 6.33l3.89-3.88a1 1 0 0 0-1.42-1.42m8.58-12.08a4.49 4.49 0 0 0-6.33 0l-3.89 3.88a1 1 0 1 0 1.42 1.42l3.88-3.88a2.52 2.52 0 0 1 3.5 0 2.47 2.47 0 0 1 0 3.5l-3.88 3.88a1 1 0 0 0 0 1.42 1 1 0 0 0 1.42 0l3.88-3.89a4.49 4.49 0 0 0 0-6.33M8.83 15.17a1 1 0 0 0 .71.29 1 1 0 0 0 .71-.29l4.92-4.92a1 1 0 1 0-1.42-1.42l-4.92 4.92a1 1 0 0 0 0 1.42"></path></svg></span><span class="sr-only" data-pagefind-ignore="">Section titled “Authentication”</span></a></div>
163
+ <p>Every <code dir="auto">/api/v1</code> route requires an API key as a bearer token. Keys are
164
+ minted in the admin under <strong>Settings → API Keys</strong> — see <a href="/docs/v1/using-velora/users-and-roles/">Users and
165
+ roles</a> for creating, listing and
166
+ revoking them. An admin session token is <em>not</em> accepted here, and an API
167
+ key is not accepted on the admin <code dir="auto">/api/*</code> routes; the two are separate
168
+ credentials on separate code paths.</p>
169
+ <p>Call it with no key at all:</p>
170
+ <div class="expressive-code"><figure class="frame is-terminal not-content"><figcaption class="header"><span class="title"></span><span class="sr-only">Terminal window</span></figcaption><pre data-language="sh"><code><div class="ec-line"><div class="code"><span style="--0:#82AAFF;--1:#3B61B0">curl</span><span style="--0:#D6DEEB;--1:#403F53"> </span><span style="--0:#82AAFF;--1:#3B61B0">-s</span><span style="--0:#D6DEEB;--1:#403F53"> </span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#ECC48D;--1:#984E4D">https://demo.velora-cms.com/api/v1/content?limit=1</span><span style="--0:#D9F5DD;--1:#111111">"</span></div></div></code></pre><div class="copy"><div aria-live="polite"></div><button title="Copy to clipboard" data-copied="Copied!" data-code="curl -s &#x22;https://demo.velora-cms.com/api/v1/content?limit=1&#x22;"><div></div></button></div></figure></div>
171
+ <p>With no <code dir="auto">Authorization</code> header, that is a <code dir="auto">401</code> — <strong>live</strong>, and identical
172
+ for every failure mode (missing header, wrong prefix, unknown key, revoked
173
+ key), so a caller cannot probe which keys exist:</p>
174
+ <div class="expressive-code"><figure class="frame not-content"><figcaption class="header"></figcaption><pre data-language="json"><code><div class="ec-line"><div class="code"><span style="--0:#D6DEEB;--1:#403F53">{</span><span style="--0:#7FDBCA;--1:#096E72">"message"</span><span style="--0:#D6DEEB;--1:#403F53">:</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#C789D6;--1:#7C5686">A valid API key is required</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#D6DEEB;--1:#403F53">}</span></div></div></code></pre><div class="copy"><div aria-live="polite"></div><button title="Copy to clipboard" data-copied="Copied!" data-code="{&#x22;message&#x22;:&#x22;A valid API key is required&#x22;}"><div></div></button></div></figure></div>
175
+ <p>Sending the key is one header:</p>
176
+ <div class="expressive-code"><figure class="frame is-terminal not-content"><figcaption class="header"><span class="title"></span><span class="sr-only">Terminal window</span></figcaption><pre data-language="sh"><code><div class="ec-line"><div class="code"><span style="--0:#82AAFF;--1:#3B61B0">curl</span><span style="--0:#D6DEEB;--1:#403F53"> </span><span style="--0:#82AAFF;--1:#3B61B0">-s</span><span style="--0:#D6DEEB;--1:#403F53"> </span><span style="--0:#82AAFF;--1:#3B61B0">-H</span><span style="--0:#D6DEEB;--1:#403F53"> </span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#ECC48D;--1:#984E4D">Authorization: Bearer vk_4ac0…9328</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#D6DEEB;--1:#403F53"> </span><span style="--0:#F78C6C;--1:#AA0982">\</span></div></div><div class="ec-line"><div class="code"><span class="indent"> </span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#ECC48D;--1:#984E4D">https://demo.velora-cms.com/api/v1/content?limit=1</span><span style="--0:#D9F5DD;--1:#111111">"</span></div></div></code></pre><div class="copy"><div aria-live="polite"></div><button title="Copy to clipboard" data-copied="Copied!" data-code="curl -s -H &#x22;Authorization: Bearer vk_4ac0…9328&#x22; \ &#x22;https://demo.velora-cms.com/api/v1/content?limit=1&#x22;"><div></div></button></div></figure></div>
177
+ <p>That returns the list envelope described under <a href="#cursor-pagination">Cursor
178
+ pagination</a> below.</p>
179
+ <div class="sl-heading-wrapper level-h3"><h3 id="what-a-key-can-do">What a key can do</h3><a class="sl-anchor-link" href="#what-a-key-can-do"><span aria-hidden="true" class="sl-anchor-icon"><svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor"><path d="m12.11 15.39-3.88 3.88a2.52 2.52 0 0 1-3.5 0 2.47 2.47 0 0 1 0-3.5l3.88-3.88a1 1 0 1 0-1.42-1.42l-3.88 3.89a4.48 4.48 0 0 0 6.33 6.33l3.89-3.88a1 1 0 0 0-1.42-1.42m8.58-12.08a4.49 4.49 0 0 0-6.33 0l-3.89 3.88a1 1 0 1 0 1.42 1.42l3.88-3.88a2.52 2.52 0 0 1 3.5 0 2.47 2.47 0 0 1 0 3.5l-3.88 3.88a1 1 0 0 0 0 1.42 1 1 0 0 0 1.42 0l3.88-3.89a4.49 4.49 0 0 0 0-6.33M8.83 15.17a1 1 0 0 0 .71.29 1 1 0 0 0 .71-.29l4.92-4.92a1 1 0 1 0-1.42-1.42l-4.92 4.92a1 1 0 0 0 0 1.42"></path></svg></span><span class="sr-only" data-pagefind-ignore="">Section titled “What a key can do”</span></a></div>
180
+ <p>Every key carries a scope, fixed when it was minted, and <strong>the default is
181
+ read</strong>. A key created without asking for anything else reaches the read
182
+ routes and nothing more.</p>
183
+
184
+
185
+
186
+
187
+
188
+
189
+
190
+
191
+
192
+
193
+
194
+
195
+
196
+
197
+
198
+
199
+
200
+
201
+
202
+
203
+
204
+
205
+
206
+ <table><thead><tr><th>Name</th><th>Type</th><th>Meaning</th><th>Example</th></tr></thead><tbody><tr><td><code dir="auto">read</code></td><td>scope</td><td>The default. Reaches every <code dir="auto">GET</code> on this page — content, document types, media, templates — and the GraphQL endpoint. Refused on all four write routes.</td><td>A key for a public website</td></tr><tr><td><code dir="auto">write</code></td><td>scope</td><td>Implies read, and additionally passes <code dir="auto">POST /api/v1/content</code>, <code dir="auto">PUT</code>/<code dir="auto">PATCH /api/v1/content/:id</code> and <code dir="auto">POST /api/v1/content/:id/publish</code>.</td><td>A key for a migration script</td></tr></tbody></table>
207
+ <p>Write access is therefore a per-key choice an administrator makes on
208
+ purpose — see <a href="/docs/v1/using-velora/users-and-roles/#create-a-key">Users and
209
+ roles</a> for the create call
210
+ and the admin form. The read routes are gated by <code dir="auto">requireApiKey</code> alone; the
211
+ four write routes run a second check on the authenticated key’s scope, and
212
+ a read key gets a <code dir="auto">403</code> with exactly this body:</p>
213
+ <div class="expressive-code"><figure class="frame not-content"><figcaption class="header"></figcaption><pre data-language="json"><code><div class="ec-line"><div class="code"><span style="--0:#D6DEEB;--1:#403F53">{</span><span style="--0:#7FDBCA;--1:#096E72">"message"</span><span style="--0:#D6DEEB;--1:#403F53">:</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#C789D6;--1:#7C5686">This API key is read-only</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#D6DEEB;--1:#403F53">}</span></div></div></code></pre><div class="copy"><div aria-live="polite"></div><button title="Copy to clipboard" data-copied="Copied!" data-code="{&#x22;message&#x22;:&#x22;This API key is read-only&#x22;}"><div></div></button></div></figure></div>
214
+ <p>That is a different failure from the <code dir="auto">401</code> above: the key is real and
215
+ unrevoked, it simply may not write.</p>
216
+ <p>So: <strong>treat a write key as a password</strong>. Blocking non-<code dir="auto">GET</code> methods at your
217
+ reverse proxy is still worth doing as a second layer — it is how the public
218
+ demo below is set up — but with scopes it is defense in depth rather than
219
+ the only thing holding the door.</p>
220
+ <p>Writes are attributed to a fixed system account, <code dir="auto">api-author@velora.internal</code>
221
+ (“Public API”, role <code dir="auto">editor</code>), created idempotently at boot. It is a
222
+ non-login account: its password hash is random bytes nobody holds the
223
+ plaintext for, so it can never sign in. Every version a key writes carries
224
+ that account as its author, which is why API writes are distinguishable
225
+ from a person’s edits in version history but not from each other.</p>
226
+ <div class="sl-heading-wrapper level-h3"><h3 id="the-public-demo">The public demo</h3><a class="sl-anchor-link" href="#the-public-demo"><span aria-hidden="true" class="sl-anchor-icon"><svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor"><path d="m12.11 15.39-3.88 3.88a2.52 2.52 0 0 1-3.5 0 2.47 2.47 0 0 1 0-3.5l3.88-3.88a1 1 0 1 0-1.42-1.42l-3.88 3.89a4.48 4.48 0 0 0 6.33 6.33l3.89-3.88a1 1 0 0 0-1.42-1.42m8.58-12.08a4.49 4.49 0 0 0-6.33 0l-3.89 3.88a1 1 0 1 0 1.42 1.42l3.88-3.88a2.52 2.52 0 0 1 3.5 0 2.47 2.47 0 0 1 0 3.5l-3.88 3.88a1 1 0 0 0 0 1.42 1 1 0 0 0 1.42 0l3.88-3.89a4.49 4.49 0 0 0 0-6.33M8.83 15.17a1 1 0 0 0 .71.29 1 1 0 0 0 .71-.29l4.92-4.92a1 1 0 1 0-1.42-1.42l-4.92 4.92a1 1 0 0 0 0 1.42"></path></svg></span><span class="sr-only" data-pagefind-ignore="">Section titled “The public demo”</span></a></div>
227
+ <p>The demo instance used for the live transcripts on this page publishes its
228
+ key on purpose:</p>
229
+
230
+
231
+
232
+
233
+
234
+
235
+
236
+
237
+
238
+
239
+
240
+
241
+
242
+
243
+
244
+
245
+
246
+
247
+
248
+
249
+
250
+
251
+
252
+ <table><thead><tr><th>Name</th><th>Type</th><th>Meaning</th><th>Example</th></tr></thead><tbody><tr><td>Demo origin</td><td>string (URL)</td><td>A live, seeded Velora instance.</td><td><code dir="auto">https://demo.velora-cms.com</code></td></tr><tr><td>Demo key</td><td>string</td><td>Intentionally public, rate-limited, rotated at will.</td><td><code dir="auto">vk_4ac0a6b78b953a13b8646e6379fcbb6bee1a269af253a14eebce668572019328</code></td></tr></tbody></table>
253
+ <p>The demo blocks every non-<code dir="auto">GET</code> method on <code dir="auto">/api/v1/</code> at its edge, with one
254
+ exception: <code dir="auto">POST /api/v1/graphql</code> is let through, because the GraphQL
255
+ schema declares no <code dir="auto">Mutation</code> type and GraphiQL posts its queries, so
256
+ there is nothing to write there. Every other non-<code dir="auto">GET</code> request — the REST
257
+ write routes below included — meets a <code dir="auto">403 Forbidden</code> in HTML before it
258
+ reaches Velora. That is a property of <em>that deployment’s proxy</em>, layered on
259
+ top of the key’s own scope rather than in place of it — the JSON <code dir="auto">403</code> from
260
+ the section above never gets a chance to be produced here:</p>
261
+ <div class="expressive-code"><figure class="frame is-terminal not-content"><figcaption class="header"><span class="title"></span><span class="sr-only">Terminal window</span></figcaption><pre data-language="sh"><code><div class="ec-line"><div class="code"><span style="--0:#82AAFF;--1:#3B61B0">curl</span><span style="--0:#D6DEEB;--1:#403F53"> </span><span style="--0:#82AAFF;--1:#3B61B0">-si</span><span style="--0:#D6DEEB;--1:#403F53"> </span><span style="--0:#82AAFF;--1:#3B61B0">-X</span><span style="--0:#D6DEEB;--1:#403F53"> </span><span style="--0:#ECC48D;--1:#3B61B0">POST</span><span style="--0:#D6DEEB;--1:#403F53"> </span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#ECC48D;--1:#984E4D">https://demo.velora-cms.com/api/v1/content</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#D6DEEB;--1:#403F53"> </span><span style="--0:#F78C6C;--1:#AA0982">\</span></div></div><div class="ec-line"><div class="code"><span class="indent"> </span><span style="--0:#82AAFF;--1:#3B61B0">-H</span><span style="--0:#D6DEEB;--1:#403F53"> </span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#ECC48D;--1:#984E4D">Authorization: Bearer vk_4ac0…9328</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#D6DEEB;--1:#403F53"> </span><span style="--0:#F78C6C;--1:#AA0982">\</span></div></div><div class="ec-line"><div class="code"><span class="indent"> </span><span style="--0:#82AAFF;--1:#3B61B0">-H</span><span style="--0:#D6DEEB;--1:#403F53"> </span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#ECC48D;--1:#984E4D">Content-Type: application/json</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#D6DEEB;--1:#403F53"> </span><span style="--0:#F78C6C;--1:#AA0982">\</span></div></div><div class="ec-line"><div class="code"><span class="indent"> </span><span style="--0:#82AAFF;--1:#3B61B0">-d</span><span style="--0:#D6DEEB;--1:#403F53"> </span><span style="--0:#D9F5DD;--1:#111111">'</span><span style="--0:#ECC48D;--1:#984E4D">{"documentTypeId":"…","parentId":null,"slug":"probe"}</span><span style="--0:#D9F5DD;--1:#111111">'</span></div></div></code></pre><div class="copy"><div aria-live="polite"></div><button title="Copy to clipboard" data-copied="Copied!" data-code="curl -si -X POST &#x22;https://demo.velora-cms.com/api/v1/content&#x22; \ -H &#x22;Authorization: Bearer vk_4ac0…9328&#x22; \ -H &#x22;Content-Type: application/json&#x22; \ -d &#x27;{&#x22;documentTypeId&#x22;:&#x22;…&#x22;,&#x22;parentId&#x22;:null,&#x22;slug&#x22;:&#x22;probe&#x22;}&#x27;"><div></div></button></div></figure></div>
262
+ <p>The response never comes from Velora at all — <strong>live</strong>, note the <code dir="auto">Server</code>
263
+ header and the HTML content type:</p>
264
+ <div class="expressive-code"><figure class="frame not-content"><figcaption class="header"></figcaption><pre data-language="plaintext"><code><div class="ec-line"><div class="code"><span style="--0:#d6deeb;--1:#403f53">HTTP/1.1 403 Forbidden</span></div></div><div class="ec-line"><div class="code"><span style="--0:#d6deeb;--1:#403f53">Server: nginx/1.24.0 (Ubuntu)</span></div></div><div class="ec-line"><div class="code"><span style="--0:#d6deeb;--1:#403f53">Content-Type: text/html</span></div></div></code></pre><div class="copy"><div aria-live="polite"></div><button title="Copy to clipboard" data-copied="Copied!" data-code="HTTP/1.1 403 ForbiddenServer: nginx/1.24.0 (Ubuntu)Content-Type: text/html"><div></div></button></div></figure></div>
265
+ <p>Write examples further down this page are therefore schema-derived, shown
266
+ against a local instance.</p>
267
+ <div class="sl-heading-wrapper level-h2"><h2 id="conventions">Conventions</h2><a class="sl-anchor-link" href="#conventions"><span aria-hidden="true" class="sl-anchor-icon"><svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor"><path d="m12.11 15.39-3.88 3.88a2.52 2.52 0 0 1-3.5 0 2.47 2.47 0 0 1 0-3.5l3.88-3.88a1 1 0 1 0-1.42-1.42l-3.88 3.89a4.48 4.48 0 0 0 6.33 6.33l3.89-3.88a1 1 0 0 0-1.42-1.42m8.58-12.08a4.49 4.49 0 0 0-6.33 0l-3.89 3.88a1 1 0 1 0 1.42 1.42l3.88-3.88a2.52 2.52 0 0 1 3.5 0 2.47 2.47 0 0 1 0 3.5l-3.88 3.88a1 1 0 0 0 0 1.42 1 1 0 0 0 1.42 0l3.88-3.89a4.49 4.49 0 0 0 0-6.33M8.83 15.17a1 1 0 0 0 .71.29 1 1 0 0 0 .71-.29l4.92-4.92a1 1 0 1 0-1.42-1.42l-4.92 4.92a1 1 0 0 0 0 1.42"></path></svg></span><span class="sr-only" data-pagefind-ignore="">Section titled “Conventions”</span></a></div>
268
+ <p>Four things behave identically across the whole surface, so they are
269
+ described once here rather than repeated per route.</p>
270
+ <div class="sl-heading-wrapper level-h3"><h3 id="cursor-pagination">Cursor pagination</h3><a class="sl-anchor-link" href="#cursor-pagination"><span aria-hidden="true" class="sl-anchor-icon"><svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor"><path d="m12.11 15.39-3.88 3.88a2.52 2.52 0 0 1-3.5 0 2.47 2.47 0 0 1 0-3.5l3.88-3.88a1 1 0 1 0-1.42-1.42l-3.88 3.89a4.48 4.48 0 0 0 6.33 6.33l3.89-3.88a1 1 0 0 0-1.42-1.42m8.58-12.08a4.49 4.49 0 0 0-6.33 0l-3.89 3.88a1 1 0 1 0 1.42 1.42l3.88-3.88a2.52 2.52 0 0 1 3.5 0 2.47 2.47 0 0 1 0 3.5l-3.88 3.88a1 1 0 0 0 0 1.42 1 1 0 0 0 1.42 0l3.88-3.89a4.49 4.49 0 0 0 0-6.33M8.83 15.17a1 1 0 0 0 .71.29 1 1 0 0 0 .71-.29l4.92-4.92a1 1 0 1 0-1.42-1.42l-4.92 4.92a1 1 0 0 0 0 1.42"></path></svg></span><span class="sr-only" data-pagefind-ignore="">Section titled “Cursor pagination”</span></a></div>
271
+ <p>Every list route answers the same envelope: an <code dir="auto">items</code> array and a
272
+ <code dir="auto">nextCursor</code>. There is no offset, no total count, and no unbounded list.</p>
273
+
274
+
275
+
276
+
277
+
278
+
279
+
280
+
281
+
282
+
283
+
284
+
285
+
286
+
287
+
288
+
289
+
290
+
291
+
292
+
293
+
294
+
295
+
296
+
297
+
298
+
299
+
300
+
301
+
302
+
303
+
304
+
305
+
306
+
307
+
308
+ <table><thead><tr><th>Name</th><th>Type</th><th>Meaning</th><th>Example</th></tr></thead><tbody><tr><td><code dir="auto">limit</code></td><td>integer 1–100</td><td>Page size. Default <code dir="auto">50</code> on every list except media, which defaults to <code dir="auto">24</code>. Out of range is a <code dir="auto">400</code>, never a silent clamp.</td><td><code dir="auto">2</code></td></tr><tr><td><code dir="auto">cursor</code></td><td>string (uuid)</td><td>The <code dir="auto">id</code> of the last item on the previous page. Omit it for the first page.</td><td><code dir="auto">45dfb6ca-ffa7-482e-842b-f662c0c1034c</code></td></tr><tr><td><code dir="auto">items</code></td><td>array</td><td>The page of rows, in tree order for content lists.</td><td><code dir="auto">[…]</code></td></tr><tr><td><code dir="auto">nextCursor</code></td><td>string (uuid) or null</td><td>The id to pass as <code dir="auto">cursor</code> for the next page. It is non-null exactly when the page came back full (<code dir="auto">items.length === limit</code>), so the last page of a perfectly-divisible list still hands you a cursor — the following request then returns an empty <code dir="auto">items</code> and <code dir="auto">null</code>.</td><td><code dir="auto">45dfb6ca-…</code></td></tr></tbody></table>
309
+ <p>Here are two consecutive pages of one node’s children on the demo, one
310
+ item at a time — first page, no cursor:</p>
311
+ <div class="expressive-code"><figure class="frame is-terminal not-content"><figcaption class="header"><span class="title"></span><span class="sr-only">Terminal window</span></figcaption><pre data-language="sh"><code><div class="ec-line"><div class="code"><span style="--0:#82AAFF;--1:#3B61B0">curl</span><span style="--0:#D6DEEB;--1:#403F53"> </span><span style="--0:#82AAFF;--1:#3B61B0">-s</span><span style="--0:#D6DEEB;--1:#403F53"> </span><span style="--0:#82AAFF;--1:#3B61B0">-H</span><span style="--0:#D6DEEB;--1:#403F53"> </span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#ECC48D;--1:#984E4D">Authorization: Bearer vk_4ac0…9328</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#D6DEEB;--1:#403F53"> </span><span style="--0:#F78C6C;--1:#AA0982">\</span></div></div><div class="ec-line"><div class="code"><span class="indent"> </span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#ECC48D;--1:#984E4D">https://demo.velora-cms.com/api/v1/content/acc3aefb-6254-465a-b088-c9578b2794af/children?limit=1</span><span style="--0:#D9F5DD;--1:#111111">"</span></div></div></code></pre><div class="copy"><div aria-live="polite"></div><button title="Copy to clipboard" data-copied="Copied!" data-code="curl -s -H &#x22;Authorization: Bearer vk_4ac0…9328&#x22; \ &#x22;https://demo.velora-cms.com/api/v1/content/acc3aefb-6254-465a-b088-c9578b2794af/children?limit=1&#x22;"><div></div></button></div></figure></div>
312
+ <p>One row comes back, and <code dir="auto">nextCursor</code> repeats its id — <strong>live</strong>:</p>
313
+ <div class="expressive-code"><figure class="frame not-content"><figcaption class="header"></figcaption><pre data-language="json"><code><div class="ec-line"><div class="code"><span style="--0:#D6DEEB;--1:#403F53">{</span><span style="--0:#7FDBCA;--1:#096E72">"items"</span><span style="--0:#D6DEEB;--1:#403F53">:[{</span><span style="--0:#7FDBCA;--1:#096E72">"id"</span><span style="--0:#D6DEEB;--1:#403F53">:</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#C789D6;--1:#7C5686">afe5b000-d81e-4c51-b60a-1f5f04d6dacf</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#D6DEEB;--1:#403F53">,</span><span style="--0:#7FDBCA;--1:#096E72">"parentId"</span><span style="--0:#D6DEEB;--1:#403F53">:</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#C789D6;--1:#7C5686">acc3aefb-6254-465a-b088-c9578b2794af</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#D6DEEB;--1:#403F53">,</span><span style="--0:#7FDBCA;--1:#096E72">"path"</span><span style="--0:#D6DEEB;--1:#403F53">:</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#C789D6;--1:#7C5686">/jane-doe/hello-velora</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#D6DEEB;--1:#403F53">,</span><span style="--0:#7FDBCA;--1:#096E72">"depth"</span><span style="--0:#D6DEEB;--1:#403F53">:</span><span style="--0:#F78C6C;--1:#AA0982">1</span><span style="--0:#D6DEEB;--1:#403F53">,</span><span style="--0:#7FDBCA;--1:#096E72">"sortOrder"</span><span style="--0:#D6DEEB;--1:#403F53">:</span><span style="--0:#F78C6C;--1:#AA0982">0</span><span style="--0:#D6DEEB;--1:#403F53">,</span><span style="--0:#7FDBCA;--1:#096E72">"documentTypeId"</span><span style="--0:#D6DEEB;--1:#403F53">:</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#C789D6;--1:#7C5686">f0491ccf-0511-43a7-862b-fdf9dce55f3c</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#D6DEEB;--1:#403F53">,</span><span style="--0:#7FDBCA;--1:#096E72">"locale"</span><span style="--0:#D6DEEB;--1:#403F53">:</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#C789D6;--1:#7C5686">en</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#D6DEEB;--1:#403F53">,</span><span style="--0:#7FDBCA;--1:#096E72">"publishedAt"</span><span style="--0:#D6DEEB;--1:#403F53">:</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#C789D6;--1:#7C5686">2026-08-25T15:36:10.252Z</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#D6DEEB;--1:#403F53">}],</span><span style="--0:#7FDBCA;--1:#096E72">"nextCursor"</span><span style="--0:#D6DEEB;--1:#403F53">:</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#C789D6;--1:#7C5686">afe5b000-d81e-4c51-b60a-1f5f04d6dacf</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#D6DEEB;--1:#403F53">}</span></div></div></code></pre><div class="copy"><div aria-live="polite"></div><button title="Copy to clipboard" data-copied="Copied!" data-code="{&#x22;items&#x22;:[{&#x22;id&#x22;:&#x22;afe5b000-d81e-4c51-b60a-1f5f04d6dacf&#x22;,&#x22;parentId&#x22;:&#x22;acc3aefb-6254-465a-b088-c9578b2794af&#x22;,&#x22;path&#x22;:&#x22;/jane-doe/hello-velora&#x22;,&#x22;depth&#x22;:1,&#x22;sortOrder&#x22;:0,&#x22;documentTypeId&#x22;:&#x22;f0491ccf-0511-43a7-862b-fdf9dce55f3c&#x22;,&#x22;locale&#x22;:&#x22;en&#x22;,&#x22;publishedAt&#x22;:&#x22;2026-08-25T15:36:10.252Z&#x22;}],&#x22;nextCursor&#x22;:&#x22;afe5b000-d81e-4c51-b60a-1f5f04d6dacf&#x22;}"><div></div></button></div></figure></div>
314
+ <p>Feeding that value back as <code dir="auto">cursor</code> asks for the page after it:</p>
315
+ <div class="expressive-code"><figure class="frame is-terminal not-content"><figcaption class="header"><span class="title"></span><span class="sr-only">Terminal window</span></figcaption><pre data-language="sh"><code><div class="ec-line"><div class="code"><span style="--0:#82AAFF;--1:#3B61B0">curl</span><span style="--0:#D6DEEB;--1:#403F53"> </span><span style="--0:#82AAFF;--1:#3B61B0">-s</span><span style="--0:#D6DEEB;--1:#403F53"> </span><span style="--0:#82AAFF;--1:#3B61B0">-H</span><span style="--0:#D6DEEB;--1:#403F53"> </span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#ECC48D;--1:#984E4D">Authorization: Bearer vk_4ac0…9328</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#D6DEEB;--1:#403F53"> </span><span style="--0:#F78C6C;--1:#AA0982">\</span></div></div><div class="ec-line"><div class="code"><span class="indent"> </span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#ECC48D;--1:#984E4D">https://demo.velora-cms.com/api/v1/content/acc3aefb-6254-465a-b088-c9578b2794af/children?limit=1&#x26;cursor=afe5b000-d81e-4c51-b60a-1f5f04d6dacf</span><span style="--0:#D9F5DD;--1:#111111">"</span></div></div></code></pre><div class="copy"><div aria-live="polite"></div><button title="Copy to clipboard" data-copied="Copied!" data-code="curl -s -H &#x22;Authorization: Bearer vk_4ac0…9328&#x22; \ &#x22;https://demo.velora-cms.com/api/v1/content/acc3aefb-6254-465a-b088-c9578b2794af/children?limit=1&#x26;cursor=afe5b000-d81e-4c51-b60a-1f5f04d6dacf&#x22;"><div></div></button></div></figure></div>
316
+ <p>The second child arrives — <strong>live</strong>. Stop when <code dir="auto">nextCursor</code> is <code dir="auto">null</code>, or
317
+ when a page comes back empty:</p>
318
+ <div class="expressive-code"><figure class="frame not-content"><figcaption class="header"></figcaption><pre data-language="json"><code><div class="ec-line"><div class="code"><span style="--0:#D6DEEB;--1:#403F53">{</span><span style="--0:#7FDBCA;--1:#096E72">"items"</span><span style="--0:#D6DEEB;--1:#403F53">:[{</span><span style="--0:#7FDBCA;--1:#096E72">"id"</span><span style="--0:#D6DEEB;--1:#403F53">:</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#C789D6;--1:#7C5686">ce16029a-f5ef-4bbe-ad07-6eede30c9b8c</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#D6DEEB;--1:#403F53">,</span><span style="--0:#7FDBCA;--1:#096E72">"parentId"</span><span style="--0:#D6DEEB;--1:#403F53">:</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#C789D6;--1:#7C5686">acc3aefb-6254-465a-b088-c9578b2794af</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#D6DEEB;--1:#403F53">,</span><span style="--0:#7FDBCA;--1:#096E72">"path"</span><span style="--0:#D6DEEB;--1:#403F53">:</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#C789D6;--1:#7C5686">/jane-doe/writing-your-first-post</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#D6DEEB;--1:#403F53">,</span><span style="--0:#7FDBCA;--1:#096E72">"depth"</span><span style="--0:#D6DEEB;--1:#403F53">:</span><span style="--0:#F78C6C;--1:#AA0982">1</span><span style="--0:#D6DEEB;--1:#403F53">,</span><span style="--0:#7FDBCA;--1:#096E72">"sortOrder"</span><span style="--0:#D6DEEB;--1:#403F53">:</span><span style="--0:#F78C6C;--1:#AA0982">1</span><span style="--0:#D6DEEB;--1:#403F53">,</span><span style="--0:#7FDBCA;--1:#096E72">"documentTypeId"</span><span style="--0:#D6DEEB;--1:#403F53">:</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#C789D6;--1:#7C5686">f0491ccf-0511-43a7-862b-fdf9dce55f3c</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#D6DEEB;--1:#403F53">,</span><span style="--0:#7FDBCA;--1:#096E72">"locale"</span><span style="--0:#D6DEEB;--1:#403F53">:</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#C789D6;--1:#7C5686">en</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#D6DEEB;--1:#403F53">,</span><span style="--0:#7FDBCA;--1:#096E72">"publishedAt"</span><span style="--0:#D6DEEB;--1:#403F53">:</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#C789D6;--1:#7C5686">2026-08-25T14:31:45.741Z</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#D6DEEB;--1:#403F53">}],</span><span style="--0:#7FDBCA;--1:#096E72">"nextCursor"</span><span style="--0:#D6DEEB;--1:#403F53">:</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#C789D6;--1:#7C5686">ce16029a-f5ef-4bbe-ad07-6eede30c9b8c</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#D6DEEB;--1:#403F53">}</span></div></div></code></pre><div class="copy"><div aria-live="polite"></div><button title="Copy to clipboard" data-copied="Copied!" data-code="{&#x22;items&#x22;:[{&#x22;id&#x22;:&#x22;ce16029a-f5ef-4bbe-ad07-6eede30c9b8c&#x22;,&#x22;parentId&#x22;:&#x22;acc3aefb-6254-465a-b088-c9578b2794af&#x22;,&#x22;path&#x22;:&#x22;/jane-doe/writing-your-first-post&#x22;,&#x22;depth&#x22;:1,&#x22;sortOrder&#x22;:1,&#x22;documentTypeId&#x22;:&#x22;f0491ccf-0511-43a7-862b-fdf9dce55f3c&#x22;,&#x22;locale&#x22;:&#x22;en&#x22;,&#x22;publishedAt&#x22;:&#x22;2026-08-25T14:31:45.741Z&#x22;}],&#x22;nextCursor&#x22;:&#x22;ce16029a-f5ef-4bbe-ad07-6eede30c9b8c&#x22;}"><div></div></button></div></figure></div>
319
+ <div class="sl-heading-wrapper level-h3"><h3 id="published-only-reads">Published-only reads</h3><a class="sl-anchor-link" href="#published-only-reads"><span aria-hidden="true" class="sl-anchor-icon"><svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor"><path d="m12.11 15.39-3.88 3.88a2.52 2.52 0 0 1-3.5 0 2.47 2.47 0 0 1 0-3.5l3.88-3.88a1 1 0 1 0-1.42-1.42l-3.88 3.89a4.48 4.48 0 0 0 6.33 6.33l3.89-3.88a1 1 0 0 0-1.42-1.42m8.58-12.08a4.49 4.49 0 0 0-6.33 0l-3.89 3.88a1 1 0 1 0 1.42 1.42l3.88-3.88a2.52 2.52 0 0 1 3.5 0 2.47 2.47 0 0 1 0 3.5l-3.88 3.88a1 1 0 0 0 0 1.42 1 1 0 0 0 1.42 0l3.88-3.89a4.49 4.49 0 0 0 0-6.33M8.83 15.17a1 1 0 0 0 .71.29 1 1 0 0 0 .71-.29l4.92-4.92a1 1 0 1 0-1.42-1.42l-4.92 4.92a1 1 0 0 0 0 1.42"></path></svg></span><span class="sr-only" data-pagefind-ignore="">Section titled “Published-only reads”</span></a></div>
320
+ <p>Every read route serves published content only, and every miss — unknown
321
+ id, unknown path, a draft, an unpublished translation — answers the same
322
+ <code dir="auto">404</code> body, <code dir="auto">{"message":"No published content found"}</code>. The uniformity is
323
+ the point: a key holder cannot use the read surface to discover which
324
+ drafts exist. The write routes are the deliberate exception; they address
325
+ a node by id in any publish state, because a key holder there is a writer
326
+ editing their own draft.</p>
327
+ <div class="sl-heading-wrapper level-h3"><h3 id="locale-and-fallback">Locale and fallback</h3><a class="sl-anchor-link" href="#locale-and-fallback"><span aria-hidden="true" class="sl-anchor-icon"><svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor"><path d="m12.11 15.39-3.88 3.88a2.52 2.52 0 0 1-3.5 0 2.47 2.47 0 0 1 0-3.5l3.88-3.88a1 1 0 1 0-1.42-1.42l-3.88 3.89a4.48 4.48 0 0 0 6.33 6.33l3.89-3.88a1 1 0 0 0-1.42-1.42m8.58-12.08a4.49 4.49 0 0 0-6.33 0l-3.89 3.88a1 1 0 1 0 1.42 1.42l3.88-3.88a2.52 2.52 0 0 1 3.5 0 2.47 2.47 0 0 1 0 3.5l-3.88 3.88a1 1 0 0 0 0 1.42 1 1 0 0 0 1.42 0l3.88-3.89a4.49 4.49 0 0 0 0-6.33M8.83 15.17a1 1 0 0 0 .71.29 1 1 0 0 0 .71-.29l4.92-4.92a1 1 0 1 0-1.42-1.42l-4.92 4.92a1 1 0 0 0 0 1.42"></path></svg></span><span class="sr-only" data-pagefind-ignore="">Section titled “Locale and fallback”</span></a></div>
328
+ <p>Item reads (<code dir="auto">/api/v1/content/:id</code> and <code dir="auto">/api/v1/content/by-path</code>) accept a
329
+ locale pair, resolved the same way as the admin API.</p>
330
+
331
+
332
+
333
+
334
+
335
+
336
+
337
+
338
+
339
+
340
+
341
+
342
+
343
+
344
+
345
+
346
+
347
+
348
+
349
+
350
+
351
+
352
+
353
+ <table><thead><tr><th>Name</th><th>Type</th><th>Meaning</th><th>Example</th></tr></thead><tbody><tr><td><code dir="auto">locale</code></td><td>string, at least 2 characters</td><td>The translation to resolve to, starting from the node you addressed.</td><td><code dir="auto">ar</code></td></tr><tr><td><code dir="auto">fallback</code></td><td>string, at least 2 characters</td><td>The locale to fall back to when the requested one is missing. A <strong>locale string</strong>, not a boolean.</td><td><code dir="auto">en</code></td></tr></tbody></table>
354
+ <p>Both the node you named and the translation resolved from it must be
355
+ published, or the read is the uniform <code dir="auto">404</code>.</p>
356
+ <div class="sl-heading-wrapper level-h3"><h3 id="preview-tokens">Preview tokens</h3><a class="sl-anchor-link" href="#preview-tokens"><span aria-hidden="true" class="sl-anchor-icon"><svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor"><path d="m12.11 15.39-3.88 3.88a2.52 2.52 0 0 1-3.5 0 2.47 2.47 0 0 1 0-3.5l3.88-3.88a1 1 0 1 0-1.42-1.42l-3.88 3.89a4.48 4.48 0 0 0 6.33 6.33l3.89-3.88a1 1 0 0 0-1.42-1.42m8.58-12.08a4.49 4.49 0 0 0-6.33 0l-3.89 3.88a1 1 0 1 0 1.42 1.42l3.88-3.88a2.52 2.52 0 0 1 3.5 0 2.47 2.47 0 0 1 0 3.5l-3.88 3.88a1 1 0 0 0 0 1.42 1 1 0 0 0 1.42 0l3.88-3.89a4.49 4.49 0 0 0 0-6.33M8.83 15.17a1 1 0 0 0 .71.29 1 1 0 0 0 .71-.29l4.92-4.92a1 1 0 1 0-1.42-1.42l-4.92 4.92a1 1 0 0 0 0 1.42"></path></svg></span><span class="sr-only" data-pagefind-ignore="">Section titled “Preview tokens”</span></a></div>
357
+ <p>A draft can be read through the public API only with a preview token. An
358
+ admin session mints one per content id with <code dir="auto">POST /api/content/:id/preview-token</code>
359
+ (an admin route, session-gated), and the public read routes accept it as a
360
+ query parameter.</p>
361
+
362
+
363
+
364
+
365
+
366
+
367
+
368
+
369
+
370
+
371
+
372
+
373
+
374
+
375
+
376
+
377
+
378
+ <table><thead><tr><th>Name</th><th>Type</th><th>Meaning</th><th>Example</th></tr></thead><tbody><tr><td><code dir="auto">previewToken</code></td><td>string</td><td><code dir="auto">base64url(payload).base64url(signature)</code> — exactly one dot, so it can never be confused with an admin session token. Signed with a key derived from <code dir="auto">JWT_SECRET</code>; expires after <code dir="auto">VELORA_PREVIEW_TOKEN_TTL</code> seconds.</td><td><code dir="auto">eyJjb250ZW50….Xq7…</code></td></tr></tbody></table>
379
+ <p>A token unlocks exactly the one node id it names, and it is checked
380
+ against the node <em>after</em> locale resolution — a token for a different node,
381
+ a tampered token, an expired token and no token at all are all treated the
382
+ same way (no unlock, uniform <code dir="auto">404</code>). A draft served this way is the only
383
+ case where <code dir="auto">publishedAt</code> comes back <code dir="auto">null</code> on a read.</p>
384
+ <div class="sl-heading-wrapper level-h2"><h2 id="content">Content</h2><a class="sl-anchor-link" href="#content"><span aria-hidden="true" class="sl-anchor-icon"><svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor"><path d="m12.11 15.39-3.88 3.88a2.52 2.52 0 0 1-3.5 0 2.47 2.47 0 0 1 0-3.5l3.88-3.88a1 1 0 1 0-1.42-1.42l-3.88 3.89a4.48 4.48 0 0 0 6.33 6.33l3.89-3.88a1 1 0 0 0-1.42-1.42m8.58-12.08a4.49 4.49 0 0 0-6.33 0l-3.89 3.88a1 1 0 1 0 1.42 1.42l3.88-3.88a2.52 2.52 0 0 1 3.5 0 2.47 2.47 0 0 1 0 3.5l-3.88 3.88a1 1 0 0 0 0 1.42 1 1 0 0 0 1.42 0l3.88-3.89a4.49 4.49 0 0 0 0-6.33M8.83 15.17a1 1 0 0 0 .71.29 1 1 0 0 0 .71-.29l4.92-4.92a1 1 0 1 0-1.42-1.42l-4.92 4.92a1 1 0 0 0 0 1.42"></path></svg></span><span class="sr-only" data-pagefind-ignore="">Section titled “Content”</span></a></div>
385
+ <div class="sl-heading-wrapper level-h3"><h3 id="get-apiv1content--search-and-filter"><code dir="auto">GET /api/v1/content</code> — search and filter</h3><a class="sl-anchor-link" href="#get-apiv1content--search-and-filter"><span aria-hidden="true" class="sl-anchor-icon"><svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor"><path d="m12.11 15.39-3.88 3.88a2.52 2.52 0 0 1-3.5 0 2.47 2.47 0 0 1 0-3.5l3.88-3.88a1 1 0 1 0-1.42-1.42l-3.88 3.89a4.48 4.48 0 0 0 6.33 6.33l3.89-3.88a1 1 0 0 0-1.42-1.42m8.58-12.08a4.49 4.49 0 0 0-6.33 0l-3.89 3.88a1 1 0 1 0 1.42 1.42l3.88-3.88a2.52 2.52 0 0 1 3.5 0 2.47 2.47 0 0 1 0 3.5l-3.88 3.88a1 1 0 0 0 0 1.42 1 1 0 0 0 1.42 0l3.88-3.89a4.49 4.49 0 0 0 0-6.33M8.83 15.17a1 1 0 0 0 .71.29 1 1 0 0 0 .71-.29l4.92-4.92a1 1 0 1 0-1.42-1.42l-4.92 4.92a1 1 0 0 0 0 1.42"></path></svg></span><span class="sr-only" data-pagefind-ignore="">Section titled “GET /api/v1/content — search and filter”</span></a></div>
386
+ <p>Combinable filters over every published node in the site.</p>
387
+
388
+
389
+
390
+
391
+
392
+
393
+
394
+
395
+
396
+
397
+
398
+
399
+
400
+
401
+
402
+
403
+
404
+
405
+
406
+
407
+
408
+
409
+
410
+
411
+
412
+
413
+
414
+
415
+
416
+
417
+
418
+
419
+
420
+
421
+
422
+
423
+
424
+
425
+
426
+
427
+
428
+ <table><thead><tr><th>Name</th><th>Type</th><th>Meaning</th><th>Example</th></tr></thead><tbody><tr><td><code dir="auto">documentTypeId</code></td><td>string (uuid), optional</td><td>Only nodes of this document type.</td><td><code dir="auto">f0491ccf-0511-43a7-862b-fdf9dce55f3c</code></td></tr><tr><td><code dir="auto">under</code></td><td>string (uuid), optional</td><td>Only strict descendants of this published node, resolved server-side to a materialised-path prefix. An unknown or unpublished anchor is a <code dir="auto">404</code>.</td><td><code dir="auto">acc3aefb-6254-465a-b088-c9578b2794af</code></td></tr><tr><td><code dir="auto">q</code></td><td>string, at least 2 characters, optional</td><td>Case-insensitive substring match. The two-character floor keeps a single character from triggering a full scan.</td><td><code dir="auto">hello</code></td></tr><tr><td><code dir="auto">cursor</code></td><td>string (uuid), optional</td><td>Pagination cursor.</td><td><code dir="auto">45dfb6ca-…</code></td></tr><tr><td><code dir="auto">limit</code></td><td>integer 1–100</td><td>Page size, default <code dir="auto">50</code>.</td><td><code dir="auto">2</code></td></tr></tbody></table>
429
+ <p>Two published nodes, page size two:</p>
430
+ <div class="expressive-code"><figure class="frame is-terminal not-content"><figcaption class="header"><span class="title"></span><span class="sr-only">Terminal window</span></figcaption><pre data-language="sh"><code><div class="ec-line"><div class="code"><span style="--0:#82AAFF;--1:#3B61B0">curl</span><span style="--0:#D6DEEB;--1:#403F53"> </span><span style="--0:#82AAFF;--1:#3B61B0">-s</span><span style="--0:#D6DEEB;--1:#403F53"> </span><span style="--0:#82AAFF;--1:#3B61B0">-H</span><span style="--0:#D6DEEB;--1:#403F53"> </span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#ECC48D;--1:#984E4D">Authorization: Bearer vk_4ac0…9328</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#D6DEEB;--1:#403F53"> </span><span style="--0:#F78C6C;--1:#AA0982">\</span></div></div><div class="ec-line"><div class="code"><span class="indent"> </span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#ECC48D;--1:#984E4D">https://demo.velora-cms.com/api/v1/content?limit=2</span><span style="--0:#D9F5DD;--1:#111111">"</span></div></div></code></pre><div class="copy"><div aria-live="polite"></div><button title="Copy to clipboard" data-copied="Copied!" data-code="curl -s -H &#x22;Authorization: Bearer vk_4ac0…9328&#x22; \ &#x22;https://demo.velora-cms.com/api/v1/content?limit=2&#x22;"><div></div></button></div></figure></div>
431
+ <p>The response is a page of <strong>summaries</strong> — <strong>live</strong>. Note there is no
432
+ <code dir="auto">data</code>: a list never carries field values, so rendering an item always
433
+ takes a second request:</p>
434
+ <div class="expressive-code"><figure class="frame not-content"><figcaption class="header"></figcaption><pre data-language="json"><code><div class="ec-line"><div class="code"><span style="--0:#D6DEEB;--1:#403F53">{</span><span style="--0:#7FDBCA;--1:#096E72">"items"</span><span style="--0:#D6DEEB;--1:#403F53">:[{</span><span style="--0:#7FDBCA;--1:#096E72">"id"</span><span style="--0:#D6DEEB;--1:#403F53">:</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#C789D6;--1:#7C5686">2e9053b1-a300-493f-a48b-58440cdf3629</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#D6DEEB;--1:#403F53">,</span><span style="--0:#7FDBCA;--1:#096E72">"parentId"</span><span style="--0:#D6DEEB;--1:#403F53">:</span><span style="--0:#FF6A83;--1:#A24848">null</span><span style="--0:#D6DEEB;--1:#403F53">,</span><span style="--0:#7FDBCA;--1:#096E72">"path"</span><span style="--0:#D6DEEB;--1:#403F53">:</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#C789D6;--1:#7C5686">/announcements</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#D6DEEB;--1:#403F53">,</span><span style="--0:#7FDBCA;--1:#096E72">"depth"</span><span style="--0:#D6DEEB;--1:#403F53">:</span><span style="--0:#F78C6C;--1:#AA0982">0</span><span style="--0:#D6DEEB;--1:#403F53">,</span><span style="--0:#7FDBCA;--1:#096E72">"sortOrder"</span><span style="--0:#D6DEEB;--1:#403F53">:</span><span style="--0:#F78C6C;--1:#AA0982">1</span><span style="--0:#D6DEEB;--1:#403F53">,</span><span style="--0:#7FDBCA;--1:#096E72">"documentTypeId"</span><span style="--0:#D6DEEB;--1:#403F53">:</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#C789D6;--1:#7C5686">d90012af-c528-4cff-b0c9-cc4c9cfc05c9</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#D6DEEB;--1:#403F53">,</span><span style="--0:#7FDBCA;--1:#096E72">"locale"</span><span style="--0:#D6DEEB;--1:#403F53">:</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#C789D6;--1:#7C5686">en</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#D6DEEB;--1:#403F53">,</span><span style="--0:#7FDBCA;--1:#096E72">"publishedAt"</span><span style="--0:#D6DEEB;--1:#403F53">:</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#C789D6;--1:#7C5686">2026-08-25T14:31:45.729Z</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#D6DEEB;--1:#403F53">},{</span><span style="--0:#7FDBCA;--1:#096E72">"id"</span><span style="--0:#D6DEEB;--1:#403F53">:</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#C789D6;--1:#7C5686">45dfb6ca-ffa7-482e-842b-f662c0c1034c</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#D6DEEB;--1:#403F53">,</span><span style="--0:#7FDBCA;--1:#096E72">"parentId"</span><span style="--0:#D6DEEB;--1:#403F53">:</span><span style="--0:#FF6A83;--1:#A24848">null</span><span style="--0:#D6DEEB;--1:#403F53">,</span><span style="--0:#7FDBCA;--1:#096E72">"path"</span><span style="--0:#D6DEEB;--1:#403F53">:</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#C789D6;--1:#7C5686">/guides</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#D6DEEB;--1:#403F53">,</span><span style="--0:#7FDBCA;--1:#096E72">"depth"</span><span style="--0:#D6DEEB;--1:#403F53">:</span><span style="--0:#F78C6C;--1:#AA0982">0</span><span style="--0:#D6DEEB;--1:#403F53">,</span><span style="--0:#7FDBCA;--1:#096E72">"sortOrder"</span><span style="--0:#D6DEEB;--1:#403F53">:</span><span style="--0:#F78C6C;--1:#AA0982">2</span><span style="--0:#D6DEEB;--1:#403F53">,</span><span style="--0:#7FDBCA;--1:#096E72">"documentTypeId"</span><span style="--0:#D6DEEB;--1:#403F53">:</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#C789D6;--1:#7C5686">d90012af-c528-4cff-b0c9-cc4c9cfc05c9</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#D6DEEB;--1:#403F53">,</span><span style="--0:#7FDBCA;--1:#096E72">"locale"</span><span style="--0:#D6DEEB;--1:#403F53">:</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#C789D6;--1:#7C5686">en</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#D6DEEB;--1:#403F53">,</span><span style="--0:#7FDBCA;--1:#096E72">"publishedAt"</span><span style="--0:#D6DEEB;--1:#403F53">:</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#C789D6;--1:#7C5686">2026-08-25T14:31:45.733Z</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#D6DEEB;--1:#403F53">}],</span><span style="--0:#7FDBCA;--1:#096E72">"nextCursor"</span><span style="--0:#D6DEEB;--1:#403F53">:</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#C789D6;--1:#7C5686">45dfb6ca-ffa7-482e-842b-f662c0c1034c</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#D6DEEB;--1:#403F53">}</span></div></div></code></pre><div class="copy"><div aria-live="polite"></div><button title="Copy to clipboard" data-copied="Copied!" data-code="{&#x22;items&#x22;:[{&#x22;id&#x22;:&#x22;2e9053b1-a300-493f-a48b-58440cdf3629&#x22;,&#x22;parentId&#x22;:null,&#x22;path&#x22;:&#x22;/announcements&#x22;,&#x22;depth&#x22;:0,&#x22;sortOrder&#x22;:1,&#x22;documentTypeId&#x22;:&#x22;d90012af-c528-4cff-b0c9-cc4c9cfc05c9&#x22;,&#x22;locale&#x22;:&#x22;en&#x22;,&#x22;publishedAt&#x22;:&#x22;2026-08-25T14:31:45.729Z&#x22;},{&#x22;id&#x22;:&#x22;45dfb6ca-ffa7-482e-842b-f662c0c1034c&#x22;,&#x22;parentId&#x22;:null,&#x22;path&#x22;:&#x22;/guides&#x22;,&#x22;depth&#x22;:0,&#x22;sortOrder&#x22;:2,&#x22;documentTypeId&#x22;:&#x22;d90012af-c528-4cff-b0c9-cc4c9cfc05c9&#x22;,&#x22;locale&#x22;:&#x22;en&#x22;,&#x22;publishedAt&#x22;:&#x22;2026-08-25T14:31:45.733Z&#x22;}],&#x22;nextCursor&#x22;:&#x22;45dfb6ca-ffa7-482e-842b-f662c0c1034c&#x22;}"><div></div></button></div></figure></div>
435
+ <p>Every property of a summary row:</p>
436
+
437
+
438
+
439
+
440
+
441
+
442
+
443
+
444
+
445
+
446
+
447
+
448
+
449
+
450
+
451
+
452
+
453
+
454
+
455
+
456
+
457
+
458
+
459
+
460
+
461
+
462
+
463
+
464
+
465
+
466
+
467
+
468
+
469
+
470
+
471
+
472
+
473
+
474
+
475
+
476
+
477
+
478
+
479
+
480
+
481
+
482
+
483
+
484
+
485
+
486
+
487
+
488
+
489
+
490
+
491
+
492
+
493
+
494
+
495
+ <table><thead><tr><th>Name</th><th>Type</th><th>Meaning</th><th>Example</th></tr></thead><tbody><tr><td><code dir="auto">id</code></td><td>string (uuid)</td><td>The node’s identity, and the <code dir="auto">:id</code> in every other content route.</td><td><code dir="auto">2e9053b1-a300-493f-a48b-58440cdf3629</code></td></tr><tr><td><code dir="auto">parentId</code></td><td>string (uuid) or null</td><td>The parent node, or <code dir="auto">null</code> for a root.</td><td><code dir="auto">acc3aefb-6254-465a-b088-c9578b2794af</code></td></tr><tr><td><code dir="auto">path</code></td><td>string</td><td>The materialised path — the node’s position in the tree, and what <code dir="auto">by-path</code> resolves.</td><td><code dir="auto">/jane-doe/hello-velora</code></td></tr><tr><td><code dir="auto">depth</code></td><td>integer, 0 or more</td><td>Number of ancestors. A root is <code dir="auto">0</code>.</td><td><code dir="auto">1</code></td></tr><tr><td><code dir="auto">sortOrder</code></td><td>integer</td><td>Position among siblings, ascending.</td><td><code dir="auto">0</code></td></tr><tr><td><code dir="auto">documentTypeId</code></td><td>string (uuid)</td><td>Which document type this node is; look it up to interpret the item’s <code dir="auto">data</code>.</td><td><code dir="auto">f0491ccf-0511-43a7-862b-fdf9dce55f3c</code></td></tr><tr><td><code dir="auto">locale</code></td><td>string</td><td>The translation this row is.</td><td><code dir="auto">en</code></td></tr><tr><td><code dir="auto">publishedAt</code></td><td>ISO datetime string</td><td>When this node was published. Never null on a list — lists are published-only by construction.</td><td><code dir="auto">2026-08-25T14:31:45.729Z</code></td></tr></tbody></table>
496
+ <div class="sl-heading-wrapper level-h3"><h3 id="get-apiv1contentroots--top-level-nodes"><code dir="auto">GET /api/v1/content/roots</code> — top-level nodes</h3><a class="sl-anchor-link" href="#get-apiv1contentroots--top-level-nodes"><span aria-hidden="true" class="sl-anchor-icon"><svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor"><path d="m12.11 15.39-3.88 3.88a2.52 2.52 0 0 1-3.5 0 2.47 2.47 0 0 1 0-3.5l3.88-3.88a1 1 0 1 0-1.42-1.42l-3.88 3.89a4.48 4.48 0 0 0 6.33 6.33l3.89-3.88a1 1 0 0 0-1.42-1.42m8.58-12.08a4.49 4.49 0 0 0-6.33 0l-3.89 3.88a1 1 0 1 0 1.42 1.42l3.88-3.88a2.52 2.52 0 0 1 3.5 0 2.47 2.47 0 0 1 0 3.5l-3.88 3.88a1 1 0 0 0 0 1.42 1 1 0 0 0 1.42 0l3.88-3.89a4.49 4.49 0 0 0 0-6.33M8.83 15.17a1 1 0 0 0 .71.29 1 1 0 0 0 .71-.29l4.92-4.92a1 1 0 1 0-1.42-1.42l-4.92 4.92a1 1 0 0 0 0 1.42"></path></svg></span><span class="sr-only" data-pagefind-ignore="">Section titled “GET /api/v1/content/roots — top-level nodes”</span></a></div>
497
+ <p>The published nodes with no parent, in sort order. Takes <code dir="auto">cursor</code> and
498
+ <code dir="auto">limit</code> only — the shared <a href="#cursor-pagination">pagination parameters</a>
499
+ above — and answers the same envelope as <code dir="auto">/api/v1/content</code>.</p>
500
+
501
+
502
+
503
+
504
+
505
+
506
+
507
+
508
+
509
+
510
+
511
+
512
+
513
+
514
+
515
+
516
+
517
+
518
+
519
+
520
+
521
+
522
+
523
+ <table><thead><tr><th>Name</th><th>Type</th><th>Meaning</th><th>Example</th></tr></thead><tbody><tr><td><code dir="auto">cursor</code></td><td>string (uuid), optional</td><td>See <a href="#cursor-pagination">Cursor pagination</a>.</td><td><code dir="auto">45dfb6ca-ffa7-482e-842b-f662c0c1034c</code></td></tr><tr><td><code dir="auto">limit</code></td><td>integer 1–100</td><td>See <a href="#cursor-pagination">Cursor pagination</a>. Default <code dir="auto">50</code>.</td><td><code dir="auto">3</code></td></tr></tbody></table>
524
+ <div class="expressive-code"><figure class="frame is-terminal not-content"><figcaption class="header"><span class="title"></span><span class="sr-only">Terminal window</span></figcaption><pre data-language="sh"><code><div class="ec-line"><div class="code"><span style="--0:#82AAFF;--1:#3B61B0">curl</span><span style="--0:#D6DEEB;--1:#403F53"> </span><span style="--0:#82AAFF;--1:#3B61B0">-s</span><span style="--0:#D6DEEB;--1:#403F53"> </span><span style="--0:#82AAFF;--1:#3B61B0">-H</span><span style="--0:#D6DEEB;--1:#403F53"> </span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#ECC48D;--1:#984E4D">Authorization: Bearer vk_4ac0…9328</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#D6DEEB;--1:#403F53"> </span><span style="--0:#F78C6C;--1:#AA0982">\</span></div></div><div class="ec-line"><div class="code"><span class="indent"> </span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#ECC48D;--1:#984E4D">https://demo.velora-cms.com/api/v1/content/roots?limit=3</span><span style="--0:#D9F5DD;--1:#111111">"</span></div></div></code></pre><div class="copy"><div aria-live="polite"></div><button title="Copy to clipboard" data-copied="Copied!" data-code="curl -s -H &#x22;Authorization: Bearer vk_4ac0…9328&#x22; \ &#x22;https://demo.velora-cms.com/api/v1/content/roots?limit=3&#x22;"><div></div></button></div></figure></div>
525
+ <p>Three roots come back — <strong>live</strong> — and this is the natural entry point for
526
+ building a navigation tree:</p>
527
+ <div class="expressive-code"><figure class="frame not-content"><figcaption class="header"></figcaption><pre data-language="json"><code><div class="ec-line"><div class="code"><span style="--0:#D6DEEB;--1:#403F53">{</span><span style="--0:#7FDBCA;--1:#096E72">"items"</span><span style="--0:#D6DEEB;--1:#403F53">:[{</span><span style="--0:#7FDBCA;--1:#096E72">"id"</span><span style="--0:#D6DEEB;--1:#403F53">:</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#C789D6;--1:#7C5686">acc3aefb-6254-465a-b088-c9578b2794af</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#D6DEEB;--1:#403F53">,</span><span style="--0:#7FDBCA;--1:#096E72">"parentId"</span><span style="--0:#D6DEEB;--1:#403F53">:</span><span style="--0:#FF6A83;--1:#A24848">null</span><span style="--0:#D6DEEB;--1:#403F53">,</span><span style="--0:#7FDBCA;--1:#096E72">"path"</span><span style="--0:#D6DEEB;--1:#403F53">:</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#C789D6;--1:#7C5686">/jane-doe</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#D6DEEB;--1:#403F53">,</span><span style="--0:#7FDBCA;--1:#096E72">"depth"</span><span style="--0:#D6DEEB;--1:#403F53">:</span><span style="--0:#F78C6C;--1:#AA0982">0</span><span style="--0:#D6DEEB;--1:#403F53">,</span><span style="--0:#7FDBCA;--1:#096E72">"sortOrder"</span><span style="--0:#D6DEEB;--1:#403F53">:</span><span style="--0:#F78C6C;--1:#AA0982">0</span><span style="--0:#D6DEEB;--1:#403F53">,</span><span style="--0:#7FDBCA;--1:#096E72">"documentTypeId"</span><span style="--0:#D6DEEB;--1:#403F53">:</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#C789D6;--1:#7C5686">511011a7-2a0b-42be-95c1-f6831d7dcbe6</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#D6DEEB;--1:#403F53">,</span><span style="--0:#7FDBCA;--1:#096E72">"locale"</span><span style="--0:#D6DEEB;--1:#403F53">:</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#C789D6;--1:#7C5686">en</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#D6DEEB;--1:#403F53">,</span><span style="--0:#7FDBCA;--1:#096E72">"publishedAt"</span><span style="--0:#D6DEEB;--1:#403F53">:</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#C789D6;--1:#7C5686">2026-08-25T14:31:45.725Z</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#D6DEEB;--1:#403F53">},{</span><span style="--0:#7FDBCA;--1:#096E72">"id"</span><span style="--0:#D6DEEB;--1:#403F53">:</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#C789D6;--1:#7C5686">2e9053b1-a300-493f-a48b-58440cdf3629</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#D6DEEB;--1:#403F53">,</span><span style="--0:#7FDBCA;--1:#096E72">"parentId"</span><span style="--0:#D6DEEB;--1:#403F53">:</span><span style="--0:#FF6A83;--1:#A24848">null</span><span style="--0:#D6DEEB;--1:#403F53">,</span><span style="--0:#7FDBCA;--1:#096E72">"path"</span><span style="--0:#D6DEEB;--1:#403F53">:</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#C789D6;--1:#7C5686">/announcements</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#D6DEEB;--1:#403F53">,</span><span style="--0:#7FDBCA;--1:#096E72">"depth"</span><span style="--0:#D6DEEB;--1:#403F53">:</span><span style="--0:#F78C6C;--1:#AA0982">0</span><span style="--0:#D6DEEB;--1:#403F53">,</span><span style="--0:#7FDBCA;--1:#096E72">"sortOrder"</span><span style="--0:#D6DEEB;--1:#403F53">:</span><span style="--0:#F78C6C;--1:#AA0982">1</span><span style="--0:#D6DEEB;--1:#403F53">,</span><span style="--0:#7FDBCA;--1:#096E72">"documentTypeId"</span><span style="--0:#D6DEEB;--1:#403F53">:</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#C789D6;--1:#7C5686">d90012af-c528-4cff-b0c9-cc4c9cfc05c9</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#D6DEEB;--1:#403F53">,</span><span style="--0:#7FDBCA;--1:#096E72">"locale"</span><span style="--0:#D6DEEB;--1:#403F53">:</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#C789D6;--1:#7C5686">en</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#D6DEEB;--1:#403F53">,</span><span style="--0:#7FDBCA;--1:#096E72">"publishedAt"</span><span style="--0:#D6DEEB;--1:#403F53">:</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#C789D6;--1:#7C5686">2026-08-25T14:31:45.729Z</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#D6DEEB;--1:#403F53">},{</span><span style="--0:#7FDBCA;--1:#096E72">"id"</span><span style="--0:#D6DEEB;--1:#403F53">:</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#C789D6;--1:#7C5686">45dfb6ca-ffa7-482e-842b-f662c0c1034c</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#D6DEEB;--1:#403F53">,</span><span style="--0:#7FDBCA;--1:#096E72">"parentId"</span><span style="--0:#D6DEEB;--1:#403F53">:</span><span style="--0:#FF6A83;--1:#A24848">null</span><span style="--0:#D6DEEB;--1:#403F53">,</span><span style="--0:#7FDBCA;--1:#096E72">"path"</span><span style="--0:#D6DEEB;--1:#403F53">:</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#C789D6;--1:#7C5686">/guides</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#D6DEEB;--1:#403F53">,</span><span style="--0:#7FDBCA;--1:#096E72">"depth"</span><span style="--0:#D6DEEB;--1:#403F53">:</span><span style="--0:#F78C6C;--1:#AA0982">0</span><span style="--0:#D6DEEB;--1:#403F53">,</span><span style="--0:#7FDBCA;--1:#096E72">"sortOrder"</span><span style="--0:#D6DEEB;--1:#403F53">:</span><span style="--0:#F78C6C;--1:#AA0982">2</span><span style="--0:#D6DEEB;--1:#403F53">,</span><span style="--0:#7FDBCA;--1:#096E72">"documentTypeId"</span><span style="--0:#D6DEEB;--1:#403F53">:</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#C789D6;--1:#7C5686">d90012af-c528-4cff-b0c9-cc4c9cfc05c9</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#D6DEEB;--1:#403F53">,</span><span style="--0:#7FDBCA;--1:#096E72">"locale"</span><span style="--0:#D6DEEB;--1:#403F53">:</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#C789D6;--1:#7C5686">en</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#D6DEEB;--1:#403F53">,</span><span style="--0:#7FDBCA;--1:#096E72">"publishedAt"</span><span style="--0:#D6DEEB;--1:#403F53">:</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#C789D6;--1:#7C5686">2026-08-25T14:31:45.733Z</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#D6DEEB;--1:#403F53">}],</span><span style="--0:#7FDBCA;--1:#096E72">"nextCursor"</span><span style="--0:#D6DEEB;--1:#403F53">:</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#C789D6;--1:#7C5686">45dfb6ca-ffa7-482e-842b-f662c0c1034c</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#D6DEEB;--1:#403F53">}</span></div></div></code></pre><div class="copy"><div aria-live="polite"></div><button title="Copy to clipboard" data-copied="Copied!" data-code="{&#x22;items&#x22;:[{&#x22;id&#x22;:&#x22;acc3aefb-6254-465a-b088-c9578b2794af&#x22;,&#x22;parentId&#x22;:null,&#x22;path&#x22;:&#x22;/jane-doe&#x22;,&#x22;depth&#x22;:0,&#x22;sortOrder&#x22;:0,&#x22;documentTypeId&#x22;:&#x22;511011a7-2a0b-42be-95c1-f6831d7dcbe6&#x22;,&#x22;locale&#x22;:&#x22;en&#x22;,&#x22;publishedAt&#x22;:&#x22;2026-08-25T14:31:45.725Z&#x22;},{&#x22;id&#x22;:&#x22;2e9053b1-a300-493f-a48b-58440cdf3629&#x22;,&#x22;parentId&#x22;:null,&#x22;path&#x22;:&#x22;/announcements&#x22;,&#x22;depth&#x22;:0,&#x22;sortOrder&#x22;:1,&#x22;documentTypeId&#x22;:&#x22;d90012af-c528-4cff-b0c9-cc4c9cfc05c9&#x22;,&#x22;locale&#x22;:&#x22;en&#x22;,&#x22;publishedAt&#x22;:&#x22;2026-08-25T14:31:45.729Z&#x22;},{&#x22;id&#x22;:&#x22;45dfb6ca-ffa7-482e-842b-f662c0c1034c&#x22;,&#x22;parentId&#x22;:null,&#x22;path&#x22;:&#x22;/guides&#x22;,&#x22;depth&#x22;:0,&#x22;sortOrder&#x22;:2,&#x22;documentTypeId&#x22;:&#x22;d90012af-c528-4cff-b0c9-cc4c9cfc05c9&#x22;,&#x22;locale&#x22;:&#x22;en&#x22;,&#x22;publishedAt&#x22;:&#x22;2026-08-25T14:31:45.733Z&#x22;}],&#x22;nextCursor&#x22;:&#x22;45dfb6ca-ffa7-482e-842b-f662c0c1034c&#x22;}"><div></div></button></div></figure></div>
528
+ <div class="sl-heading-wrapper level-h3"><h3 id="get-apiv1contentidchildren--one-level-down"><code dir="auto">GET /api/v1/content/:id/children</code> — one level down</h3><a class="sl-anchor-link" href="#get-apiv1contentidchildren--one-level-down"><span aria-hidden="true" class="sl-anchor-icon"><svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor"><path d="m12.11 15.39-3.88 3.88a2.52 2.52 0 0 1-3.5 0 2.47 2.47 0 0 1 0-3.5l3.88-3.88a1 1 0 1 0-1.42-1.42l-3.88 3.89a4.48 4.48 0 0 0 6.33 6.33l3.89-3.88a1 1 0 0 0-1.42-1.42m8.58-12.08a4.49 4.49 0 0 0-6.33 0l-3.89 3.88a1 1 0 1 0 1.42 1.42l3.88-3.88a2.52 2.52 0 0 1 3.5 0 2.47 2.47 0 0 1 0 3.5l-3.88 3.88a1 1 0 0 0 0 1.42 1 1 0 0 0 1.42 0l3.88-3.89a4.49 4.49 0 0 0 0-6.33M8.83 15.17a1 1 0 0 0 .71.29 1 1 0 0 0 .71-.29l4.92-4.92a1 1 0 1 0-1.42-1.42l-4.92 4.92a1 1 0 0 0 0 1.42"></path></svg></span><span class="sr-only" data-pagefind-ignore="">Section titled “GET /api/v1/content/:id/children — one level down”</span></a></div>
529
+ <p>Direct children of a published node, paginated. The parent itself must be
530
+ published, or the answer is the uniform <code dir="auto">404</code> — an unpublished branch
531
+ hides its whole subtree from this route.</p>
532
+
533
+
534
+
535
+
536
+
537
+
538
+
539
+
540
+
541
+
542
+
543
+
544
+
545
+
546
+
547
+
548
+
549
+
550
+
551
+
552
+
553
+
554
+
555
+
556
+
557
+
558
+
559
+
560
+
561
+ <table><thead><tr><th>Name</th><th>Type</th><th>Meaning</th><th>Example</th></tr></thead><tbody><tr><td><code dir="auto">:id</code></td><td>string (uuid), path parameter</td><td>The parent node. A non-uuid value fails validation with a <code dir="auto">400</code>.</td><td><code dir="auto">acc3aefb-6254-465a-b088-c9578b2794af</code></td></tr><tr><td><code dir="auto">cursor</code></td><td>string (uuid), optional</td><td>Pagination cursor.</td><td><code dir="auto">afe5b000-…</code></td></tr><tr><td><code dir="auto">limit</code></td><td>integer 1–100</td><td>Page size, default <code dir="auto">50</code>.</td><td><code dir="auto">1</code></td></tr></tbody></table>
562
+ <p>The two-page example under <a href="#cursor-pagination">Cursor pagination</a> uses
563
+ this route; the envelope and row shape are identical to <code dir="auto">/api/v1/content</code>.</p>
564
+ <div class="sl-heading-wrapper level-h3"><h3 id="get-apiv1contentid--one-item-with-its-fields"><code dir="auto">GET /api/v1/content/:id</code> — one item with its fields</h3><a class="sl-anchor-link" href="#get-apiv1contentid--one-item-with-its-fields"><span aria-hidden="true" class="sl-anchor-icon"><svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor"><path d="m12.11 15.39-3.88 3.88a2.52 2.52 0 0 1-3.5 0 2.47 2.47 0 0 1 0-3.5l3.88-3.88a1 1 0 1 0-1.42-1.42l-3.88 3.89a4.48 4.48 0 0 0 6.33 6.33l3.89-3.88a1 1 0 0 0-1.42-1.42m8.58-12.08a4.49 4.49 0 0 0-6.33 0l-3.89 3.88a1 1 0 1 0 1.42 1.42l3.88-3.88a2.52 2.52 0 0 1 3.5 0 2.47 2.47 0 0 1 0 3.5l-3.88 3.88a1 1 0 0 0 0 1.42 1 1 0 0 0 1.42 0l3.88-3.89a4.49 4.49 0 0 0 0-6.33M8.83 15.17a1 1 0 0 0 .71.29 1 1 0 0 0 .71-.29l4.92-4.92a1 1 0 1 0-1.42-1.42l-4.92 4.92a1 1 0 0 0 0 1.42"></path></svg></span><span class="sr-only" data-pagefind-ignore="">Section titled “GET /api/v1/content/:id — one item with its fields”</span></a></div>
565
+ <p>The full item: core identity plus the published version’s field data.</p>
566
+
567
+
568
+
569
+
570
+
571
+
572
+
573
+
574
+
575
+
576
+
577
+
578
+
579
+
580
+
581
+
582
+
583
+
584
+
585
+
586
+
587
+
588
+
589
+
590
+
591
+
592
+
593
+
594
+
595
+
596
+
597
+
598
+
599
+
600
+
601
+
602
+
603
+
604
+
605
+
606
+
607
+ <table><thead><tr><th>Name</th><th>Type</th><th>Meaning</th><th>Example</th></tr></thead><tbody><tr><td><code dir="auto">:id</code></td><td>string (uuid), path parameter</td><td>The node to read.</td><td><code dir="auto">acc3aefb-6254-465a-b088-c9578b2794af</code></td></tr><tr><td><code dir="auto">locale</code></td><td>string, at least 2 characters, optional</td><td>Resolve to this translation.</td><td><code dir="auto">ar</code></td></tr><tr><td><code dir="auto">fallback</code></td><td>string, at least 2 characters, optional</td><td>Locale to fall back to.</td><td><code dir="auto">en</code></td></tr><tr><td><code dir="auto">format</code></td><td>string <code dir="auto">&#x3C;pluginId>:&#x3C;name></code>, optional</td><td>Replace the whole response with a plugin-owned projection. Must match <code dir="auto">^[a-z0-9.-]+:[a-z0-9-]+$</code>.</td><td><code dir="auto">com.example.seo:sitemap</code></td></tr><tr><td><code dir="auto">previewToken</code></td><td>string, optional</td><td>Unlock this one node’s draft.</td><td><code dir="auto">eyJjb250ZW50….Xq7…</code></td></tr></tbody></table>
608
+ <p>Reading the demo’s Author node:</p>
609
+ <div class="expressive-code"><figure class="frame is-terminal not-content"><figcaption class="header"><span class="title"></span><span class="sr-only">Terminal window</span></figcaption><pre data-language="sh"><code><div class="ec-line"><div class="code"><span style="--0:#82AAFF;--1:#3B61B0">curl</span><span style="--0:#D6DEEB;--1:#403F53"> </span><span style="--0:#82AAFF;--1:#3B61B0">-s</span><span style="--0:#D6DEEB;--1:#403F53"> </span><span style="--0:#82AAFF;--1:#3B61B0">-H</span><span style="--0:#D6DEEB;--1:#403F53"> </span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#ECC48D;--1:#984E4D">Authorization: Bearer vk_4ac0…9328</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#D6DEEB;--1:#403F53"> </span><span style="--0:#F78C6C;--1:#AA0982">\</span></div></div><div class="ec-line"><div class="code"><span class="indent"> </span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#ECC48D;--1:#984E4D">https://demo.velora-cms.com/api/v1/content/acc3aefb-6254-465a-b088-c9578b2794af</span><span style="--0:#D9F5DD;--1:#111111">"</span></div></div></code></pre><div class="copy"><div aria-live="polite"></div><button title="Copy to clipboard" data-copied="Copied!" data-code="curl -s -H &#x22;Authorization: Bearer vk_4ac0…9328&#x22; \ &#x22;https://demo.velora-cms.com/api/v1/content/acc3aefb-6254-465a-b088-c9578b2794af&#x22;"><div></div></button></div></figure></div>
610
+ <p>The item arrives with its fields under <code dir="auto">data</code> — <strong>live</strong>. <code dir="auto">bio</code> is a rich
611
+ text field, so its value is a document object, not an HTML string:</p>
612
+ <div class="expressive-code"><figure class="frame not-content"><figcaption class="header"></figcaption><pre data-language="json"><code><div class="ec-line"><div class="code"><span style="--0:#D6DEEB;--1:#403F53">{</span><span style="--0:#7FDBCA;--1:#096E72">"id"</span><span style="--0:#D6DEEB;--1:#403F53">:</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#C789D6;--1:#7C5686">acc3aefb-6254-465a-b088-c9578b2794af</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#D6DEEB;--1:#403F53">,</span><span style="--0:#7FDBCA;--1:#096E72">"documentTypeId"</span><span style="--0:#D6DEEB;--1:#403F53">:</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#C789D6;--1:#7C5686">511011a7-2a0b-42be-95c1-f6831d7dcbe6</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#D6DEEB;--1:#403F53">,</span><span style="--0:#7FDBCA;--1:#096E72">"locale"</span><span style="--0:#D6DEEB;--1:#403F53">:</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#C789D6;--1:#7C5686">en</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#D6DEEB;--1:#403F53">,</span><span style="--0:#7FDBCA;--1:#096E72">"publishedAt"</span><span style="--0:#D6DEEB;--1:#403F53">:</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#C789D6;--1:#7C5686">2026-08-25T14:31:45.725Z</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#D6DEEB;--1:#403F53">,</span><span style="--0:#7FDBCA;--1:#096E72">"version"</span><span style="--0:#D6DEEB;--1:#403F53">:</span><span style="--0:#F78C6C;--1:#AA0982">1</span><span style="--0:#D6DEEB;--1:#403F53">,</span><span style="--0:#7FDBCA;--1:#096E72">"data"</span><span style="--0:#D6DEEB;--1:#403F53">:{</span><span style="--0:#7FDBCA;--1:#096E72">"bio"</span><span style="--0:#D6DEEB;--1:#403F53">:{</span><span style="--0:#7FDBCA;--1:#096E72">"type"</span><span style="--0:#D6DEEB;--1:#403F53">:</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#C789D6;--1:#7C5686">doc</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#D6DEEB;--1:#403F53">,</span><span style="--0:#7FDBCA;--1:#096E72">"content"</span><span style="--0:#D6DEEB;--1:#403F53">:[{</span><span style="--0:#7FDBCA;--1:#096E72">"type"</span><span style="--0:#D6DEEB;--1:#403F53">:</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#C789D6;--1:#7C5686">paragraph</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#D6DEEB;--1:#403F53">,</span><span style="--0:#7FDBCA;--1:#096E72">"content"</span><span style="--0:#D6DEEB;--1:#403F53">:[{</span><span style="--0:#7FDBCA;--1:#096E72">"text"</span><span style="--0:#D6DEEB;--1:#403F53">:</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#C789D6;--1:#7C5686">Founding editor of this blog.</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#D6DEEB;--1:#403F53">,</span><span style="--0:#7FDBCA;--1:#096E72">"type"</span><span style="--0:#D6DEEB;--1:#403F53">:</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#C789D6;--1:#7C5686">text</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#D6DEEB;--1:#403F53">}]}]},</span><span style="--0:#7FDBCA;--1:#096E72">"name"</span><span style="--0:#D6DEEB;--1:#403F53">:</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#C789D6;--1:#7C5686">Jane Doe</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#D6DEEB;--1:#403F53">}}</span></div></div></code></pre><div class="copy"><div aria-live="polite"></div><button title="Copy to clipboard" data-copied="Copied!" data-code="{&#x22;id&#x22;:&#x22;acc3aefb-6254-465a-b088-c9578b2794af&#x22;,&#x22;documentTypeId&#x22;:&#x22;511011a7-2a0b-42be-95c1-f6831d7dcbe6&#x22;,&#x22;locale&#x22;:&#x22;en&#x22;,&#x22;publishedAt&#x22;:&#x22;2026-08-25T14:31:45.725Z&#x22;,&#x22;version&#x22;:1,&#x22;data&#x22;:{&#x22;bio&#x22;:{&#x22;type&#x22;:&#x22;doc&#x22;,&#x22;content&#x22;:[{&#x22;type&#x22;:&#x22;paragraph&#x22;,&#x22;content&#x22;:[{&#x22;text&#x22;:&#x22;Founding editor of this blog.&#x22;,&#x22;type&#x22;:&#x22;text&#x22;}]}]},&#x22;name&#x22;:&#x22;Jane Doe&#x22;}}"><div></div></button></div></figure></div>
613
+ <p>Every property of an item:</p>
614
+
615
+
616
+
617
+
618
+
619
+
620
+
621
+
622
+
623
+
624
+
625
+
626
+
627
+
628
+
629
+
630
+
631
+
632
+
633
+
634
+
635
+
636
+
637
+
638
+
639
+
640
+
641
+
642
+
643
+
644
+
645
+
646
+
647
+
648
+
649
+
650
+
651
+
652
+
653
+
654
+
655
+
656
+
657
+
658
+
659
+
660
+
661
+
662
+
663
+
664
+
665
+
666
+
667
+ <table><thead><tr><th>Name</th><th>Type</th><th>Meaning</th><th>Example</th></tr></thead><tbody><tr><td><code dir="auto">id</code></td><td>string (uuid)</td><td>The node’s identity.</td><td><code dir="auto">acc3aefb-6254-465a-b088-c9578b2794af</code></td></tr><tr><td><code dir="auto">documentTypeId</code></td><td>string (uuid)</td><td>The document type whose fields describe <code dir="auto">data</code>.</td><td><code dir="auto">511011a7-2a0b-42be-95c1-f6831d7dcbe6</code></td></tr><tr><td><code dir="auto">locale</code></td><td>string, at least 2 characters</td><td>The translation actually served, after locale/fallback resolution.</td><td><code dir="auto">en</code></td></tr><tr><td><code dir="auto">publishedAt</code></td><td>ISO datetime string or null</td><td>When this node was published. <code dir="auto">null</code> only for a draft served through a valid <code dir="auto">previewToken</code>.</td><td><code dir="auto">2026-08-25T14:31:45.725Z</code></td></tr><tr><td><code dir="auto">version</code></td><td>positive integer</td><td>Which immutable version this is. Every save mints a new one; publishing repoints at one.</td><td><code dir="auto">3</code></td></tr><tr><td><code dir="auto">data</code></td><td>object</td><td>Field values keyed by field <strong>id</strong>, exactly as the document type defines them. Values are whatever the field’s data type stores — a string, a number, an ISO date string, or a nested object for rich text and structured fields.</td><td><code dir="auto">{"name":"Jane Doe"}</code></td></tr><tr><td><code dir="auto">extensions</code></td><td>object, absent when unused</td><td>Present only when an active plugin contributed to this response. Keyed by plugin id, one nested object per plugin, structurally separate from <code dir="auto">data</code> so a plugin can never overwrite a field.</td><td><code dir="auto">{"com.example.seo":{"canonical":"/jane-doe"}}</code></td></tr></tbody></table>
668
+ <p>Asking for a <code dir="auto">format</code> no active plugin provides is a <code dir="auto">400</code>, never a silent
669
+ fallback to the default shape:</p>
670
+ <div class="expressive-code"><figure class="frame is-terminal not-content"><figcaption class="header"><span class="title"></span><span class="sr-only">Terminal window</span></figcaption><pre data-language="sh"><code><div class="ec-line"><div class="code"><span style="--0:#82AAFF;--1:#3B61B0">curl</span><span style="--0:#D6DEEB;--1:#403F53"> </span><span style="--0:#82AAFF;--1:#3B61B0">-s</span><span style="--0:#D6DEEB;--1:#403F53"> </span><span style="--0:#82AAFF;--1:#3B61B0">-H</span><span style="--0:#D6DEEB;--1:#403F53"> </span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#ECC48D;--1:#984E4D">Authorization: Bearer vk_4ac0…9328</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#D6DEEB;--1:#403F53"> </span><span style="--0:#F78C6C;--1:#AA0982">\</span></div></div><div class="ec-line"><div class="code"><span class="indent"> </span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#ECC48D;--1:#984E4D">https://demo.velora-cms.com/api/v1/content/2e9053b1-a300-493f-a48b-58440cdf3629?format=nope:thing</span><span style="--0:#D9F5DD;--1:#111111">"</span></div></div></code></pre><div class="copy"><div aria-live="polite"></div><button title="Copy to clipboard" data-copied="Copied!" data-code="curl -s -H &#x22;Authorization: Bearer vk_4ac0…9328&#x22; \ &#x22;https://demo.velora-cms.com/api/v1/content/2e9053b1-a300-493f-a48b-58440cdf3629?format=nope:thing&#x22;"><div></div></button></div></figure></div>
671
+ <p>The message names the projection you asked for — <strong>live</strong>:</p>
672
+ <div class="expressive-code"><figure class="frame not-content"><figcaption class="header"></figcaption><pre data-language="json"><code><div class="ec-line"><div class="code"><span style="--0:#D6DEEB;--1:#403F53">{</span><span style="--0:#7FDBCA;--1:#096E72">"message"</span><span style="--0:#D6DEEB;--1:#403F53">:</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#C789D6;--1:#7C5686">Unknown format nope:thing</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#D6DEEB;--1:#403F53">}</span></div></div></code></pre><div class="copy"><div aria-live="polite"></div><button title="Copy to clipboard" data-copied="Copied!" data-code="{&#x22;message&#x22;:&#x22;Unknown format nope:thing&#x22;}"><div></div></button></div></figure></div>
673
+ <p>A successful <code dir="auto">format</code> read replaces the whole body with two properties
674
+ instead — <code dir="auto">{"format": "&#x3C;pluginId>:&#x3C;name>", "content": …}</code>, where <code dir="auto">content</code>
675
+ is whatever the plugin built. Contributing one is a plugin-authoring
676
+ topic; see
677
+ <a href="https://docs.velora-cms.com/v1/building-plugins/">Building plugins</a>.</p>
678
+ <div class="sl-heading-wrapper level-h3"><h3 id="get-apiv1contentby-path--resolve-a-url"><code dir="auto">GET /api/v1/content/by-path</code> — resolve a URL</h3><a class="sl-anchor-link" href="#get-apiv1contentby-path--resolve-a-url"><span aria-hidden="true" class="sl-anchor-icon"><svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor"><path d="m12.11 15.39-3.88 3.88a2.52 2.52 0 0 1-3.5 0 2.47 2.47 0 0 1 0-3.5l3.88-3.88a1 1 0 1 0-1.42-1.42l-3.88 3.89a4.48 4.48 0 0 0 6.33 6.33l3.89-3.88a1 1 0 0 0-1.42-1.42m8.58-12.08a4.49 4.49 0 0 0-6.33 0l-3.89 3.88a1 1 0 1 0 1.42 1.42l3.88-3.88a2.52 2.52 0 0 1 3.5 0 2.47 2.47 0 0 1 0 3.5l-3.88 3.88a1 1 0 0 0 0 1.42 1 1 0 0 0 1.42 0l3.88-3.89a4.49 4.49 0 0 0 0-6.33M8.83 15.17a1 1 0 0 0 .71.29 1 1 0 0 0 .71-.29l4.92-4.92a1 1 0 1 0-1.42-1.42l-4.92 4.92a1 1 0 0 0 0 1.42"></path></svg></span><span class="sr-only" data-pagefind-ignore="">Section titled “GET /api/v1/content/by-path — resolve a URL”</span></a></div>
679
+ <p>The same item shape as <code dir="auto">/api/v1/content/:id</code>, addressed by tree path
680
+ rather than id. This is how a frontend turns a request URL into content
681
+ without storing ids anywhere.</p>
682
+
683
+
684
+
685
+
686
+
687
+
688
+
689
+
690
+
691
+
692
+
693
+
694
+
695
+
696
+
697
+
698
+
699
+
700
+
701
+
702
+
703
+
704
+
705
+
706
+
707
+
708
+
709
+
710
+
711
+
712
+
713
+
714
+
715
+
716
+
717
+
718
+
719
+
720
+
721
+
722
+
723
+ <table><thead><tr><th>Name</th><th>Type</th><th>Meaning</th><th>Example</th></tr></thead><tbody><tr><td><code dir="auto">path</code></td><td>string starting with <code dir="auto">/</code>, required</td><td>The node’s materialised path. URL-encode it in the query string.</td><td><code dir="auto">/announcements</code></td></tr><tr><td><code dir="auto">locale</code></td><td>string, at least 2 characters, optional</td><td>Resolve to this translation.</td><td><code dir="auto">ar</code></td></tr><tr><td><code dir="auto">fallback</code></td><td>string, at least 2 characters, optional</td><td>Locale to fall back to.</td><td><code dir="auto">en</code></td></tr><tr><td><code dir="auto">format</code></td><td>string <code dir="auto">&#x3C;pluginId>:&#x3C;name></code>, optional</td><td>Plugin-owned projection, as above.</td><td><code dir="auto">com.example.seo:sitemap</code></td></tr><tr><td><code dir="auto">previewToken</code></td><td>string, optional</td><td>Unlock this one node’s draft. With a valid token the path lookup itself may resolve an unpublished node.</td><td><code dir="auto">eyJjb250ZW50….Xq7…</code></td></tr></tbody></table>
724
+ <p>Resolving <code dir="auto">/announcements</code> on the demo:</p>
725
+ <div class="expressive-code"><figure class="frame is-terminal not-content"><figcaption class="header"><span class="title"></span><span class="sr-only">Terminal window</span></figcaption><pre data-language="sh"><code><div class="ec-line"><div class="code"><span style="--0:#82AAFF;--1:#3B61B0">curl</span><span style="--0:#D6DEEB;--1:#403F53"> </span><span style="--0:#82AAFF;--1:#3B61B0">-s</span><span style="--0:#D6DEEB;--1:#403F53"> </span><span style="--0:#82AAFF;--1:#3B61B0">-H</span><span style="--0:#D6DEEB;--1:#403F53"> </span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#ECC48D;--1:#984E4D">Authorization: Bearer vk_4ac0…9328</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#D6DEEB;--1:#403F53"> </span><span style="--0:#F78C6C;--1:#AA0982">\</span></div></div><div class="ec-line"><div class="code"><span class="indent"> </span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#ECC48D;--1:#984E4D">https://demo.velora-cms.com/api/v1/content/by-path?path=/announcements</span><span style="--0:#D9F5DD;--1:#111111">"</span></div></div></code></pre><div class="copy"><div aria-live="polite"></div><button title="Copy to clipboard" data-copied="Copied!" data-code="curl -s -H &#x22;Authorization: Bearer vk_4ac0…9328&#x22; \ &#x22;https://demo.velora-cms.com/api/v1/content/by-path?path=/announcements&#x22;"><div></div></button></div></figure></div>
726
+ <p>The item comes back in the identical shape a by-id read returns — <strong>live</strong>:</p>
727
+ <div class="expressive-code"><figure class="frame not-content"><figcaption class="header"></figcaption><pre data-language="json"><code><div class="ec-line"><div class="code"><span style="--0:#D6DEEB;--1:#403F53">{</span><span style="--0:#7FDBCA;--1:#096E72">"id"</span><span style="--0:#D6DEEB;--1:#403F53">:</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#C789D6;--1:#7C5686">2e9053b1-a300-493f-a48b-58440cdf3629</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#D6DEEB;--1:#403F53">,</span><span style="--0:#7FDBCA;--1:#096E72">"documentTypeId"</span><span style="--0:#D6DEEB;--1:#403F53">:</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#C789D6;--1:#7C5686">d90012af-c528-4cff-b0c9-cc4c9cfc05c9</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#D6DEEB;--1:#403F53">,</span><span style="--0:#7FDBCA;--1:#096E72">"locale"</span><span style="--0:#D6DEEB;--1:#403F53">:</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#C789D6;--1:#7C5686">en</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#D6DEEB;--1:#403F53">,</span><span style="--0:#7FDBCA;--1:#096E72">"publishedAt"</span><span style="--0:#D6DEEB;--1:#403F53">:</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#C789D6;--1:#7C5686">2026-08-25T14:31:45.729Z</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#D6DEEB;--1:#403F53">,</span><span style="--0:#7FDBCA;--1:#096E72">"version"</span><span style="--0:#D6DEEB;--1:#403F53">:</span><span style="--0:#F78C6C;--1:#AA0982">1</span><span style="--0:#D6DEEB;--1:#403F53">,</span><span style="--0:#7FDBCA;--1:#096E72">"data"</span><span style="--0:#D6DEEB;--1:#403F53">:{</span><span style="--0:#7FDBCA;--1:#096E72">"label"</span><span style="--0:#D6DEEB;--1:#403F53">:</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#C789D6;--1:#7C5686">Announcements</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#D6DEEB;--1:#403F53">,</span><span style="--0:#7FDBCA;--1:#096E72">"description"</span><span style="--0:#D6DEEB;--1:#403F53">:</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#C789D6;--1:#7C5686">Site news.</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#D6DEEB;--1:#403F53">}}</span></div></div></code></pre><div class="copy"><div aria-live="polite"></div><button title="Copy to clipboard" data-copied="Copied!" data-code="{&#x22;id&#x22;:&#x22;2e9053b1-a300-493f-a48b-58440cdf3629&#x22;,&#x22;documentTypeId&#x22;:&#x22;d90012af-c528-4cff-b0c9-cc4c9cfc05c9&#x22;,&#x22;locale&#x22;:&#x22;en&#x22;,&#x22;publishedAt&#x22;:&#x22;2026-08-25T14:31:45.729Z&#x22;,&#x22;version&#x22;:1,&#x22;data&#x22;:{&#x22;label&#x22;:&#x22;Announcements&#x22;,&#x22;description&#x22;:&#x22;Site news.&#x22;}}"><div></div></button></div></figure></div>
728
+ <div class="sl-heading-wrapper level-h2"><h2 id="writing-content">Writing content</h2><a class="sl-anchor-link" href="#writing-content"><span aria-hidden="true" class="sl-anchor-icon"><svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor"><path d="m12.11 15.39-3.88 3.88a2.52 2.52 0 0 1-3.5 0 2.47 2.47 0 0 1 0-3.5l3.88-3.88a1 1 0 1 0-1.42-1.42l-3.88 3.89a4.48 4.48 0 0 0 6.33 6.33l3.89-3.88a1 1 0 0 0-1.42-1.42m8.58-12.08a4.49 4.49 0 0 0-6.33 0l-3.89 3.88a1 1 0 1 0 1.42 1.42l3.88-3.88a2.52 2.52 0 0 1 3.5 0 2.47 2.47 0 0 1 0 3.5l-3.88 3.88a1 1 0 0 0 0 1.42 1 1 0 0 0 1.42 0l3.88-3.89a4.49 4.49 0 0 0 0-6.33M8.83 15.17a1 1 0 0 0 .71.29 1 1 0 0 0 .71-.29l4.92-4.92a1 1 0 1 0-1.42-1.42l-4.92 4.92a1 1 0 0 0 0 1.42"></path></svg></span><span class="sr-only" data-pagefind-ignore="">Section titled “Writing content”</span></a></div>
729
+ <p>Four routes write. They run the <code dir="auto">requireApiKey</code> gate the reads run and then
730
+ a second check that the authenticated key’s scope is <code dir="auto">write</code> — a read key
731
+ stops there with <code dir="auto">403 {"message":"This API key is read-only"}</code>. They address
732
+ a node in <strong>any</strong> publish state, and never mutate a row: every save inserts
733
+ a new immutable version, and publishing only repoints which version is
734
+ current.</p>
735
+ <p>The demo blocks these methods at its edge, so the transcripts in this
736
+ section are schema-derived against a local instance — ids and timestamps
737
+ are illustrative.</p>
738
+ <div class="sl-heading-wrapper level-h3"><h3 id="post-apiv1content--create-a-node"><code dir="auto">POST /api/v1/content</code> — create a node</h3><a class="sl-anchor-link" href="#post-apiv1content--create-a-node"><span aria-hidden="true" class="sl-anchor-icon"><svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor"><path d="m12.11 15.39-3.88 3.88a2.52 2.52 0 0 1-3.5 0 2.47 2.47 0 0 1 0-3.5l3.88-3.88a1 1 0 1 0-1.42-1.42l-3.88 3.89a4.48 4.48 0 0 0 6.33 6.33l3.89-3.88a1 1 0 0 0-1.42-1.42m8.58-12.08a4.49 4.49 0 0 0-6.33 0l-3.89 3.88a1 1 0 1 0 1.42 1.42l3.88-3.88a2.52 2.52 0 0 1 3.5 0 2.47 2.47 0 0 1 0 3.5l-3.88 3.88a1 1 0 0 0 0 1.42 1 1 0 0 0 1.42 0l3.88-3.89a4.49 4.49 0 0 0 0-6.33M8.83 15.17a1 1 0 0 0 .71.29 1 1 0 0 0 .71-.29l4.92-4.92a1 1 0 1 0-1.42-1.42l-4.92 4.92a1 1 0 0 0 0 1.42"></path></svg></span><span class="sr-only" data-pagefind-ignore="">Section titled “POST /api/v1/content — create a node”</span></a></div>
739
+ <p>The server derives <code dir="auto">path</code> and <code dir="auto">depth</code> from <code dir="auto">parentId</code> plus <code dir="auto">slug</code>, so an
740
+ external consumer never computes those tree internals. <code dir="auto">sortOrder</code> is
741
+ <em>not</em> derived — every node created through this route is hardcoded to
742
+ <code dir="auto">sortOrder: 0</code>, so siblings created via the public API all land at
743
+ position <code dir="auto">0</code> and need reordering afterward if their relative order
744
+ matters.</p>
745
+
746
+
747
+
748
+
749
+
750
+
751
+
752
+
753
+
754
+
755
+
756
+
757
+
758
+
759
+
760
+
761
+
762
+
763
+
764
+
765
+
766
+
767
+
768
+
769
+
770
+
771
+
772
+
773
+
774
+
775
+
776
+
777
+
778
+
779
+
780
+
781
+
782
+
783
+
784
+
785
+
786
+
787
+
788
+
789
+
790
+
791
+
792
+
793
+
794
+
795
+
796
+
797
+
798
+ <table><thead><tr><th>Name</th><th>Type</th><th>Meaning</th><th>Example</th></tr></thead><tbody><tr><td><code dir="auto">documentTypeId</code></td><td>string (uuid), required</td><td>The type of node to create. Unknown = <code dir="auto">400</code>.</td><td><code dir="auto">f0491ccf-0511-43a7-862b-fdf9dce55f3c</code></td></tr><tr><td><code dir="auto">parentId</code></td><td>string (uuid) or null, required</td><td>Parent node, or <code dir="auto">null</code> for a root. A parent id that does not exist is a <code dir="auto">404</code>.</td><td><code dir="auto">acc3aefb-6254-465a-b088-c9578b2794af</code></td></tr><tr><td><code dir="auto">slug</code></td><td>string, required</td><td>One already-slugified path segment, matching <code dir="auto">^[a-z0-9]+(?:-[a-z0-9]+)*$</code>. Appended to the parent’s path.</td><td><code dir="auto">hello-world</code></td></tr><tr><td><code dir="auto">locale</code></td><td>string, at least 2 characters</td><td>Which translation this node is. Default <code dir="auto">"en"</code>.</td><td><code dir="auto">en</code></td></tr><tr><td><code dir="auto">isDefaultLocale</code></td><td>boolean</td><td>Whether this is the translation group’s default. Default <code dir="auto">true</code>.</td><td><code dir="auto">true</code></td></tr><tr><td><code dir="auto">translationGroupId</code></td><td>string (uuid), optional</td><td>Join an existing translation group instead of starting one.</td><td><code dir="auto">7f3c1a90-4b28-4d6e-9c15-0a3e8b7d2f64</code></td></tr><tr><td><code dir="auto">data</code></td><td>object</td><td>Field values keyed by field id. Default <code dir="auto">{}</code> — an empty object creates a stub to fill in later with <code dir="auto">PATCH</code>. Non-empty data is fully field-validated against the document type.</td><td><code dir="auto">{"label":"Hello"}</code></td></tr></tbody></table>
799
+ <p>Creating a root node with its fields in one call:</p>
800
+ <div class="expressive-code"><figure class="frame is-terminal not-content"><figcaption class="header"><span class="title"></span><span class="sr-only">Terminal window</span></figcaption><pre data-language="sh"><code><div class="ec-line"><div class="code"><span style="--0:#82AAFF;--1:#3B61B0">curl</span><span style="--0:#D6DEEB;--1:#403F53"> </span><span style="--0:#82AAFF;--1:#3B61B0">-s</span><span style="--0:#D6DEEB;--1:#403F53"> </span><span style="--0:#82AAFF;--1:#3B61B0">-X</span><span style="--0:#D6DEEB;--1:#403F53"> </span><span style="--0:#ECC48D;--1:#3B61B0">POST</span><span style="--0:#D6DEEB;--1:#403F53"> </span><span style="--0:#ECC48D;--1:#3B61B0">http://localhost:3000/api/v1/content</span><span style="--0:#D6DEEB;--1:#403F53"> </span><span style="--0:#F78C6C;--1:#AA0982">\</span></div></div><div class="ec-line"><div class="code"><span class="indent"> </span><span style="--0:#82AAFF;--1:#3B61B0">-H</span><span style="--0:#D6DEEB;--1:#403F53"> </span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#ECC48D;--1:#984E4D">Authorization: Bearer </span><span style="--0:#C5E478;--1:#3B61B0">$VELORA_API_KEY</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#D6DEEB;--1:#403F53"> </span><span style="--0:#F78C6C;--1:#AA0982">\</span></div></div><div class="ec-line"><div class="code"><span class="indent"> </span><span style="--0:#82AAFF;--1:#3B61B0">-H</span><span style="--0:#D6DEEB;--1:#403F53"> </span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#ECC48D;--1:#984E4D">Content-Type: application/json</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#D6DEEB;--1:#403F53"> </span><span style="--0:#F78C6C;--1:#AA0982">\</span></div></div><div class="ec-line"><div class="code"><span class="indent"> </span><span style="--0:#82AAFF;--1:#3B61B0">-d</span><span style="--0:#D6DEEB;--1:#403F53"> </span><span style="--0:#D9F5DD;--1:#111111">'</span><span style="--0:#ECC48D;--1:#984E4D">{</span></div></div><div class="ec-line"><div class="code"><span class="indent"><span style="--0:#ECC48D;--1:#984E4D"> </span></span><span style="--0:#ECC48D;--1:#984E4D">"documentTypeId": "d90012af-c528-4cff-b0c9-cc4c9cfc05c9",</span></div></div><div class="ec-line"><div class="code"><span class="indent"><span style="--0:#ECC48D;--1:#984E4D"> </span></span><span style="--0:#ECC48D;--1:#984E4D">"parentId": null,</span></div></div><div class="ec-line"><div class="code"><span class="indent"><span style="--0:#ECC48D;--1:#984E4D"> </span></span><span style="--0:#ECC48D;--1:#984E4D">"slug": "hello-world",</span></div></div><div class="ec-line"><div class="code"><span class="indent"><span style="--0:#ECC48D;--1:#984E4D"> </span></span><span style="--0:#ECC48D;--1:#984E4D">"data": { "label": "Hello world", "description": "First post." }</span></div></div><div class="ec-line"><div class="code"><span class="indent"><span style="--0:#ECC48D;--1:#984E4D"> </span></span><span style="--0:#ECC48D;--1:#984E4D">}</span><span style="--0:#D9F5DD;--1:#111111">'</span></div></div></code></pre><div class="copy"><div aria-live="polite"></div><button title="Copy to clipboard" data-copied="Copied!" data-code="curl -s -X POST http://localhost:3000/api/v1/content \ -H &#x22;Authorization: Bearer $VELORA_API_KEY&#x22; \ -H &#x22;Content-Type: application/json&#x22; \ -d &#x27;{ &#x22;documentTypeId&#x22;: &#x22;d90012af-c528-4cff-b0c9-cc4c9cfc05c9&#x22;, &#x22;parentId&#x22;: null, &#x22;slug&#x22;: &#x22;hello-world&#x22;, &#x22;data&#x22;: { &#x22;label&#x22;: &#x22;Hello world&#x22;, &#x22;description&#x22;: &#x22;First post.&#x22; } }&#x27;"><div></div></button></div></figure></div>
801
+ <p>A success is <code dir="auto">201</code> with the node and its first version — note <code dir="auto">path</code> and
802
+ <code dir="auto">version</code>, both server-derived:</p>
803
+ <div class="expressive-code"><figure class="frame not-content"><figcaption class="header"></figcaption><pre data-language="json"><code><div class="ec-line"><div class="code"><span style="--0:#D6DEEB;--1:#403F53">{</span></div></div><div class="ec-line"><div class="code"><span class="indent"> </span><span style="--0:#7FDBCA;--1:#096E72">"id"</span><span style="--0:#D6DEEB;--1:#403F53">: </span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#C789D6;--1:#7C5686">b7c91f42-3d18-4a67-9e05-1c8f2b6d4a30</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#D6DEEB;--1:#403F53">,</span></div></div><div class="ec-line"><div class="code"><span class="indent"> </span><span style="--0:#7FDBCA;--1:#096E72">"documentTypeId"</span><span style="--0:#D6DEEB;--1:#403F53">: </span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#C789D6;--1:#7C5686">d90012af-c528-4cff-b0c9-cc4c9cfc05c9</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#D6DEEB;--1:#403F53">,</span></div></div><div class="ec-line"><div class="code"><span class="indent"> </span><span style="--0:#7FDBCA;--1:#096E72">"parentId"</span><span style="--0:#D6DEEB;--1:#403F53">: </span><span style="--0:#FF6A83;--1:#A24848">null</span><span style="--0:#D6DEEB;--1:#403F53">,</span></div></div><div class="ec-line"><div class="code"><span class="indent"> </span><span style="--0:#7FDBCA;--1:#096E72">"path"</span><span style="--0:#D6DEEB;--1:#403F53">: </span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#C789D6;--1:#7C5686">/hello-world</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#D6DEEB;--1:#403F53">,</span></div></div><div class="ec-line"><div class="code"><span class="indent"> </span><span style="--0:#7FDBCA;--1:#096E72">"locale"</span><span style="--0:#D6DEEB;--1:#403F53">: </span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#C789D6;--1:#7C5686">en</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#D6DEEB;--1:#403F53">,</span></div></div><div class="ec-line"><div class="code"><span class="indent"> </span><span style="--0:#7FDBCA;--1:#096E72">"version"</span><span style="--0:#D6DEEB;--1:#403F53">: </span><span style="--0:#F78C6C;--1:#AA0982">1</span><span style="--0:#D6DEEB;--1:#403F53">,</span></div></div><div class="ec-line"><div class="code"><span class="indent"> </span><span style="--0:#7FDBCA;--1:#096E72">"publishedAt"</span><span style="--0:#D6DEEB;--1:#403F53">: </span><span style="--0:#FF6A83;--1:#A24848">null</span><span style="--0:#D6DEEB;--1:#403F53">,</span></div></div><div class="ec-line"><div class="code"><span class="indent"> </span><span style="--0:#7FDBCA;--1:#096E72">"data"</span><span style="--0:#D6DEEB;--1:#403F53">: { </span><span style="--0:#7FDBCA;--1:#096E72">"label"</span><span style="--0:#D6DEEB;--1:#403F53">: </span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#C789D6;--1:#7C5686">Hello world</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#D6DEEB;--1:#403F53">, </span><span style="--0:#7FDBCA;--1:#096E72">"description"</span><span style="--0:#D6DEEB;--1:#403F53">: </span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#C789D6;--1:#7C5686">First post.</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#D6DEEB;--1:#403F53"> }</span></div></div><div class="ec-line"><div class="code"><span style="--0:#D6DEEB;--1:#403F53">}</span></div></div></code></pre><div class="copy"><div aria-live="polite"></div><button title="Copy to clipboard" data-copied="Copied!" data-code="{ &#x22;id&#x22;: &#x22;b7c91f42-3d18-4a67-9e05-1c8f2b6d4a30&#x22;, &#x22;documentTypeId&#x22;: &#x22;d90012af-c528-4cff-b0c9-cc4c9cfc05c9&#x22;, &#x22;parentId&#x22;: null, &#x22;path&#x22;: &#x22;/hello-world&#x22;, &#x22;locale&#x22;: &#x22;en&#x22;, &#x22;version&#x22;: 1, &#x22;publishedAt&#x22;: null, &#x22;data&#x22;: { &#x22;label&#x22;: &#x22;Hello world&#x22;, &#x22;description&#x22;: &#x22;First post.&#x22; }}"><div></div></button></div></figure></div>
804
+ <p>Every property of a write response — the same shape for create, update and
805
+ publish:</p>
806
+
807
+
808
+
809
+
810
+
811
+
812
+
813
+
814
+
815
+
816
+
817
+
818
+
819
+
820
+
821
+
822
+
823
+
824
+
825
+
826
+
827
+
828
+
829
+
830
+
831
+
832
+
833
+
834
+
835
+
836
+
837
+
838
+
839
+
840
+
841
+
842
+
843
+
844
+
845
+
846
+
847
+
848
+
849
+
850
+
851
+
852
+
853
+
854
+
855
+
856
+
857
+
858
+
859
+
860
+
861
+
862
+
863
+
864
+
865
+ <table><thead><tr><th>Name</th><th>Type</th><th>Meaning</th><th>Example</th></tr></thead><tbody><tr><td><code dir="auto">id</code></td><td>string (uuid)</td><td>The node’s identity, needed for every follow-up call.</td><td><code dir="auto">b7c91f42-3d18-4a67-9e05-1c8f2b6d4a30</code></td></tr><tr><td><code dir="auto">documentTypeId</code></td><td>string (uuid)</td><td>The node’s document type.</td><td><code dir="auto">d90012af-c528-4cff-b0c9-cc4c9cfc05c9</code></td></tr><tr><td><code dir="auto">parentId</code></td><td>string (uuid) or null</td><td>Parent node, or <code dir="auto">null</code> for a root.</td><td><code dir="auto">null</code></td></tr><tr><td><code dir="auto">path</code></td><td>string</td><td>Derived from the parent’s path and the slug. Not settable.</td><td><code dir="auto">/hello-world</code></td></tr><tr><td><code dir="auto">locale</code></td><td>string, at least 2 characters</td><td>The node’s locale.</td><td><code dir="auto">en</code></td></tr><tr><td><code dir="auto">version</code></td><td>positive integer</td><td>The version this write created — or, for publish, the one now current.</td><td><code dir="auto">1</code></td></tr><tr><td><code dir="auto">publishedAt</code></td><td>ISO datetime string or null</td><td><code dir="auto">null</code> until the node is published — a fresh node is a draft.</td><td><code dir="auto">null</code></td></tr><tr><td><code dir="auto">data</code></td><td>object</td><td>The field values now stored in that version.</td><td><code dir="auto">{"label":"Hello world"}</code></td></tr></tbody></table>
866
+ <div class="sl-heading-wrapper level-h3"><h3 id="put-apiv1contentid--replace-all-fields"><code dir="auto">PUT /api/v1/content/:id</code> — replace all fields</h3><a class="sl-anchor-link" href="#put-apiv1contentid--replace-all-fields"><span aria-hidden="true" class="sl-anchor-icon"><svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor"><path d="m12.11 15.39-3.88 3.88a2.52 2.52 0 0 1-3.5 0 2.47 2.47 0 0 1 0-3.5l3.88-3.88a1 1 0 1 0-1.42-1.42l-3.88 3.89a4.48 4.48 0 0 0 6.33 6.33l3.89-3.88a1 1 0 0 0-1.42-1.42m8.58-12.08a4.49 4.49 0 0 0-6.33 0l-3.89 3.88a1 1 0 1 0 1.42 1.42l3.88-3.88a2.52 2.52 0 0 1 3.5 0 2.47 2.47 0 0 1 0 3.5l-3.88 3.88a1 1 0 0 0 0 1.42 1 1 0 0 0 1.42 0l3.88-3.89a4.49 4.49 0 0 0 0-6.33M8.83 15.17a1 1 0 0 0 .71.29 1 1 0 0 0 .71-.29l4.92-4.92a1 1 0 1 0-1.42-1.42l-4.92 4.92a1 1 0 0 0 0 1.42"></path></svg></span><span class="sr-only" data-pagefind-ignore="">Section titled “PUT /api/v1/content/:id — replace all fields”</span></a></div>
867
+ <p>The node’s data becomes exactly the request body’s <code dir="auto">data</code>, as a new
868
+ version. The whole record is field-validated, so omitting a required field
869
+ is a <code dir="auto">400</code>.</p>
870
+
871
+
872
+
873
+
874
+
875
+
876
+
877
+
878
+
879
+
880
+
881
+
882
+
883
+
884
+
885
+
886
+
887
+
888
+
889
+
890
+
891
+
892
+
893
+ <table><thead><tr><th>Name</th><th>Type</th><th>Meaning</th><th>Example</th></tr></thead><tbody><tr><td><code dir="auto">:id</code></td><td>string (uuid), path parameter</td><td>The node to write, in any publish state. Unknown = <code dir="auto">404</code>.</td><td><code dir="auto">b7c91f42-3d18-4a67-9e05-1c8f2b6d4a30</code></td></tr><tr><td><code dir="auto">data</code></td><td>object, required</td><td>The complete new field set. Anything not listed is gone from the new version.</td><td><code dir="auto">{"label":"Hello, world"}</code></td></tr></tbody></table>
894
+ <p>Replacing both fields of the node created above:</p>
895
+ <div class="expressive-code"><figure class="frame is-terminal not-content"><figcaption class="header"><span class="title"></span><span class="sr-only">Terminal window</span></figcaption><pre data-language="sh"><code><div class="ec-line"><div class="code"><span style="--0:#82AAFF;--1:#3B61B0">curl</span><span style="--0:#D6DEEB;--1:#403F53"> </span><span style="--0:#82AAFF;--1:#3B61B0">-s</span><span style="--0:#D6DEEB;--1:#403F53"> </span><span style="--0:#82AAFF;--1:#3B61B0">-X</span><span style="--0:#D6DEEB;--1:#403F53"> </span><span style="--0:#ECC48D;--1:#3B61B0">PUT</span><span style="--0:#D6DEEB;--1:#403F53"> </span><span style="--0:#ECC48D;--1:#3B61B0">http://localhost:3000/api/v1/content/b7c91f42-3d18-4a67-9e05-1c8f2b6d4a30</span><span style="--0:#D6DEEB;--1:#403F53"> </span><span style="--0:#F78C6C;--1:#AA0982">\</span></div></div><div class="ec-line"><div class="code"><span class="indent"> </span><span style="--0:#82AAFF;--1:#3B61B0">-H</span><span style="--0:#D6DEEB;--1:#403F53"> </span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#ECC48D;--1:#984E4D">Authorization: Bearer </span><span style="--0:#C5E478;--1:#3B61B0">$VELORA_API_KEY</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#D6DEEB;--1:#403F53"> </span><span style="--0:#F78C6C;--1:#AA0982">\</span></div></div><div class="ec-line"><div class="code"><span class="indent"> </span><span style="--0:#82AAFF;--1:#3B61B0">-H</span><span style="--0:#D6DEEB;--1:#403F53"> </span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#ECC48D;--1:#984E4D">Content-Type: application/json</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#D6DEEB;--1:#403F53"> </span><span style="--0:#F78C6C;--1:#AA0982">\</span></div></div><div class="ec-line"><div class="code"><span class="indent"> </span><span style="--0:#82AAFF;--1:#3B61B0">-d</span><span style="--0:#D6DEEB;--1:#403F53"> </span><span style="--0:#D9F5DD;--1:#111111">'</span><span style="--0:#ECC48D;--1:#984E4D">{"data": {"label": "Hello, world", "description": "Rewritten."}}</span><span style="--0:#D9F5DD;--1:#111111">'</span></div></div></code></pre><div class="copy"><div aria-live="polite"></div><button title="Copy to clipboard" data-copied="Copied!" data-code="curl -s -X PUT http://localhost:3000/api/v1/content/b7c91f42-3d18-4a67-9e05-1c8f2b6d4a30 \ -H &#x22;Authorization: Bearer $VELORA_API_KEY&#x22; \ -H &#x22;Content-Type: application/json&#x22; \ -d &#x27;{&#x22;data&#x22;: {&#x22;label&#x22;: &#x22;Hello, world&#x22;, &#x22;description&#x22;: &#x22;Rewritten.&#x22;}}&#x27;"><div></div></button></div></figure></div>
896
+ <p>A success is <code dir="auto">200</code> with the write-response shape and <code dir="auto">version</code> incremented
897
+ — version 1 still exists, untouched:</p>
898
+ <div class="expressive-code"><figure class="frame not-content"><figcaption class="header"></figcaption><pre data-language="json"><code><div class="ec-line"><div class="code"><span style="--0:#D6DEEB;--1:#403F53">{</span></div></div><div class="ec-line"><div class="code"><span class="indent"> </span><span style="--0:#7FDBCA;--1:#096E72">"id"</span><span style="--0:#D6DEEB;--1:#403F53">: </span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#C789D6;--1:#7C5686">b7c91f42-3d18-4a67-9e05-1c8f2b6d4a30</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#D6DEEB;--1:#403F53">,</span></div></div><div class="ec-line"><div class="code"><span class="indent"> </span><span style="--0:#7FDBCA;--1:#096E72">"documentTypeId"</span><span style="--0:#D6DEEB;--1:#403F53">: </span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#C789D6;--1:#7C5686">d90012af-c528-4cff-b0c9-cc4c9cfc05c9</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#D6DEEB;--1:#403F53">,</span></div></div><div class="ec-line"><div class="code"><span class="indent"> </span><span style="--0:#7FDBCA;--1:#096E72">"parentId"</span><span style="--0:#D6DEEB;--1:#403F53">: </span><span style="--0:#FF6A83;--1:#A24848">null</span><span style="--0:#D6DEEB;--1:#403F53">,</span></div></div><div class="ec-line"><div class="code"><span class="indent"> </span><span style="--0:#7FDBCA;--1:#096E72">"path"</span><span style="--0:#D6DEEB;--1:#403F53">: </span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#C789D6;--1:#7C5686">/hello-world</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#D6DEEB;--1:#403F53">,</span></div></div><div class="ec-line"><div class="code"><span class="indent"> </span><span style="--0:#7FDBCA;--1:#096E72">"locale"</span><span style="--0:#D6DEEB;--1:#403F53">: </span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#C789D6;--1:#7C5686">en</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#D6DEEB;--1:#403F53">,</span></div></div><div class="ec-line"><div class="code"><span class="indent"> </span><span style="--0:#7FDBCA;--1:#096E72">"version"</span><span style="--0:#D6DEEB;--1:#403F53">: </span><span style="--0:#F78C6C;--1:#AA0982">2</span><span style="--0:#D6DEEB;--1:#403F53">,</span></div></div><div class="ec-line"><div class="code"><span class="indent"> </span><span style="--0:#7FDBCA;--1:#096E72">"publishedAt"</span><span style="--0:#D6DEEB;--1:#403F53">: </span><span style="--0:#FF6A83;--1:#A24848">null</span><span style="--0:#D6DEEB;--1:#403F53">,</span></div></div><div class="ec-line"><div class="code"><span class="indent"> </span><span style="--0:#7FDBCA;--1:#096E72">"data"</span><span style="--0:#D6DEEB;--1:#403F53">: { </span><span style="--0:#7FDBCA;--1:#096E72">"label"</span><span style="--0:#D6DEEB;--1:#403F53">: </span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#C789D6;--1:#7C5686">Hello, world</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#D6DEEB;--1:#403F53">, </span><span style="--0:#7FDBCA;--1:#096E72">"description"</span><span style="--0:#D6DEEB;--1:#403F53">: </span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#C789D6;--1:#7C5686">Rewritten.</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#D6DEEB;--1:#403F53"> }</span></div></div><div class="ec-line"><div class="code"><span style="--0:#D6DEEB;--1:#403F53">}</span></div></div></code></pre><div class="copy"><div aria-live="polite"></div><button title="Copy to clipboard" data-copied="Copied!" data-code="{ &#x22;id&#x22;: &#x22;b7c91f42-3d18-4a67-9e05-1c8f2b6d4a30&#x22;, &#x22;documentTypeId&#x22;: &#x22;d90012af-c528-4cff-b0c9-cc4c9cfc05c9&#x22;, &#x22;parentId&#x22;: null, &#x22;path&#x22;: &#x22;/hello-world&#x22;, &#x22;locale&#x22;: &#x22;en&#x22;, &#x22;version&#x22;: 2, &#x22;publishedAt&#x22;: null, &#x22;data&#x22;: { &#x22;label&#x22;: &#x22;Hello, world&#x22;, &#x22;description&#x22;: &#x22;Rewritten.&#x22; }}"><div></div></button></div></figure></div>
899
+ <div class="sl-heading-wrapper level-h3"><h3 id="patch-apiv1contentid--change-some-fields"><code dir="auto">PATCH /api/v1/content/:id</code> — change some fields</h3><a class="sl-anchor-link" href="#patch-apiv1contentid--change-some-fields"><span aria-hidden="true" class="sl-anchor-icon"><svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor"><path d="m12.11 15.39-3.88 3.88a2.52 2.52 0 0 1-3.5 0 2.47 2.47 0 0 1 0-3.5l3.88-3.88a1 1 0 1 0-1.42-1.42l-3.88 3.89a4.48 4.48 0 0 0 6.33 6.33l3.89-3.88a1 1 0 0 0-1.42-1.42m8.58-12.08a4.49 4.49 0 0 0-6.33 0l-3.89 3.88a1 1 0 1 0 1.42 1.42l3.88-3.88a2.52 2.52 0 0 1 3.5 0 2.47 2.47 0 0 1 0 3.5l-3.88 3.88a1 1 0 0 0 0 1.42 1 1 0 0 0 1.42 0l3.88-3.89a4.49 4.49 0 0 0 0-6.33M8.83 15.17a1 1 0 0 0 .71.29 1 1 0 0 0 .71-.29l4.92-4.92a1 1 0 1 0-1.42-1.42l-4.92 4.92a1 1 0 0 0 0 1.42"></path></svg></span><span class="sr-only" data-pagefind-ignore="">Section titled “PATCH /api/v1/content/:id — change some fields”</span></a></div>
900
+ <p>The body’s <code dir="auto">data</code> keys are shallow-merged over the node’s latest version.
901
+ The merge is per top-level key: an object or array field value is replaced
902
+ whole, never deep-merged. The merged result is field-validated, so omitting
903
+ a required field the node already has is fine.</p>
904
+
905
+
906
+
907
+
908
+
909
+
910
+
911
+
912
+
913
+
914
+
915
+
916
+
917
+
918
+
919
+
920
+
921
+
922
+
923
+
924
+
925
+
926
+
927
+ <table><thead><tr><th>Name</th><th>Type</th><th>Meaning</th><th>Example</th></tr></thead><tbody><tr><td><code dir="auto">:id</code></td><td>string (uuid), path parameter</td><td>The node to write, in any publish state.</td><td><code dir="auto">b7c91f42-3d18-4a67-9e05-1c8f2b6d4a30</code></td></tr><tr><td><code dir="auto">data</code></td><td>object, required</td><td>Only the keys you want to change.</td><td><code dir="auto">{"description":"Edited."}</code></td></tr></tbody></table>
928
+ <p>Changing one field and leaving the other alone:</p>
929
+ <div class="expressive-code"><figure class="frame is-terminal not-content"><figcaption class="header"><span class="title"></span><span class="sr-only">Terminal window</span></figcaption><pre data-language="sh"><code><div class="ec-line"><div class="code"><span style="--0:#82AAFF;--1:#3B61B0">curl</span><span style="--0:#D6DEEB;--1:#403F53"> </span><span style="--0:#82AAFF;--1:#3B61B0">-s</span><span style="--0:#D6DEEB;--1:#403F53"> </span><span style="--0:#82AAFF;--1:#3B61B0">-X</span><span style="--0:#D6DEEB;--1:#403F53"> </span><span style="--0:#ECC48D;--1:#3B61B0">PATCH</span><span style="--0:#D6DEEB;--1:#403F53"> </span><span style="--0:#ECC48D;--1:#3B61B0">http://localhost:3000/api/v1/content/b7c91f42-3d18-4a67-9e05-1c8f2b6d4a30</span><span style="--0:#D6DEEB;--1:#403F53"> </span><span style="--0:#F78C6C;--1:#AA0982">\</span></div></div><div class="ec-line"><div class="code"><span class="indent"> </span><span style="--0:#82AAFF;--1:#3B61B0">-H</span><span style="--0:#D6DEEB;--1:#403F53"> </span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#ECC48D;--1:#984E4D">Authorization: Bearer </span><span style="--0:#C5E478;--1:#3B61B0">$VELORA_API_KEY</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#D6DEEB;--1:#403F53"> </span><span style="--0:#F78C6C;--1:#AA0982">\</span></div></div><div class="ec-line"><div class="code"><span class="indent"> </span><span style="--0:#82AAFF;--1:#3B61B0">-H</span><span style="--0:#D6DEEB;--1:#403F53"> </span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#ECC48D;--1:#984E4D">Content-Type: application/json</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#D6DEEB;--1:#403F53"> </span><span style="--0:#F78C6C;--1:#AA0982">\</span></div></div><div class="ec-line"><div class="code"><span class="indent"> </span><span style="--0:#82AAFF;--1:#3B61B0">-d</span><span style="--0:#D6DEEB;--1:#403F53"> </span><span style="--0:#D9F5DD;--1:#111111">'</span><span style="--0:#ECC48D;--1:#984E4D">{"data": {"description": "Edited."}}</span><span style="--0:#D9F5DD;--1:#111111">'</span></div></div></code></pre><div class="copy"><div aria-live="polite"></div><button title="Copy to clipboard" data-copied="Copied!" data-code="curl -s -X PATCH http://localhost:3000/api/v1/content/b7c91f42-3d18-4a67-9e05-1c8f2b6d4a30 \ -H &#x22;Authorization: Bearer $VELORA_API_KEY&#x22; \ -H &#x22;Content-Type: application/json&#x22; \ -d &#x27;{&#x22;data&#x22;: {&#x22;description&#x22;: &#x22;Edited.&#x22;}}&#x27;"><div></div></button></div></figure></div>
930
+ <p>A success is <code dir="auto">200</code>; the untouched <code dir="auto">label</code> is carried into the new version
931
+ by the merge:</p>
932
+ <div class="expressive-code"><figure class="frame not-content"><figcaption class="header"></figcaption><pre data-language="json"><code><div class="ec-line"><div class="code"><span style="--0:#D6DEEB;--1:#403F53">{</span></div></div><div class="ec-line"><div class="code"><span class="indent"> </span><span style="--0:#7FDBCA;--1:#096E72">"id"</span><span style="--0:#D6DEEB;--1:#403F53">: </span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#C789D6;--1:#7C5686">b7c91f42-3d18-4a67-9e05-1c8f2b6d4a30</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#D6DEEB;--1:#403F53">,</span></div></div><div class="ec-line"><div class="code"><span class="indent"> </span><span style="--0:#7FDBCA;--1:#096E72">"documentTypeId"</span><span style="--0:#D6DEEB;--1:#403F53">: </span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#C789D6;--1:#7C5686">d90012af-c528-4cff-b0c9-cc4c9cfc05c9</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#D6DEEB;--1:#403F53">,</span></div></div><div class="ec-line"><div class="code"><span class="indent"> </span><span style="--0:#7FDBCA;--1:#096E72">"parentId"</span><span style="--0:#D6DEEB;--1:#403F53">: </span><span style="--0:#FF6A83;--1:#A24848">null</span><span style="--0:#D6DEEB;--1:#403F53">,</span></div></div><div class="ec-line"><div class="code"><span class="indent"> </span><span style="--0:#7FDBCA;--1:#096E72">"path"</span><span style="--0:#D6DEEB;--1:#403F53">: </span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#C789D6;--1:#7C5686">/hello-world</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#D6DEEB;--1:#403F53">,</span></div></div><div class="ec-line"><div class="code"><span class="indent"> </span><span style="--0:#7FDBCA;--1:#096E72">"locale"</span><span style="--0:#D6DEEB;--1:#403F53">: </span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#C789D6;--1:#7C5686">en</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#D6DEEB;--1:#403F53">,</span></div></div><div class="ec-line"><div class="code"><span class="indent"> </span><span style="--0:#7FDBCA;--1:#096E72">"version"</span><span style="--0:#D6DEEB;--1:#403F53">: </span><span style="--0:#F78C6C;--1:#AA0982">3</span><span style="--0:#D6DEEB;--1:#403F53">,</span></div></div><div class="ec-line"><div class="code"><span class="indent"> </span><span style="--0:#7FDBCA;--1:#096E72">"publishedAt"</span><span style="--0:#D6DEEB;--1:#403F53">: </span><span style="--0:#FF6A83;--1:#A24848">null</span><span style="--0:#D6DEEB;--1:#403F53">,</span></div></div><div class="ec-line"><div class="code"><span class="indent"> </span><span style="--0:#7FDBCA;--1:#096E72">"data"</span><span style="--0:#D6DEEB;--1:#403F53">: { </span><span style="--0:#7FDBCA;--1:#096E72">"label"</span><span style="--0:#D6DEEB;--1:#403F53">: </span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#C789D6;--1:#7C5686">Hello, world</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#D6DEEB;--1:#403F53">, </span><span style="--0:#7FDBCA;--1:#096E72">"description"</span><span style="--0:#D6DEEB;--1:#403F53">: </span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#C789D6;--1:#7C5686">Edited.</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#D6DEEB;--1:#403F53"> }</span></div></div><div class="ec-line"><div class="code"><span style="--0:#D6DEEB;--1:#403F53">}</span></div></div></code></pre><div class="copy"><div aria-live="polite"></div><button title="Copy to clipboard" data-copied="Copied!" data-code="{ &#x22;id&#x22;: &#x22;b7c91f42-3d18-4a67-9e05-1c8f2b6d4a30&#x22;, &#x22;documentTypeId&#x22;: &#x22;d90012af-c528-4cff-b0c9-cc4c9cfc05c9&#x22;, &#x22;parentId&#x22;: null, &#x22;path&#x22;: &#x22;/hello-world&#x22;, &#x22;locale&#x22;: &#x22;en&#x22;, &#x22;version&#x22;: 3, &#x22;publishedAt&#x22;: null, &#x22;data&#x22;: { &#x22;label&#x22;: &#x22;Hello, world&#x22;, &#x22;description&#x22;: &#x22;Edited.&#x22; }}"><div></div></button></div></figure></div>
933
+ <p>A write that fails validation is a <code dir="auto">400</code> carrying per-field failures:</p>
934
+
935
+
936
+
937
+
938
+
939
+
940
+
941
+
942
+
943
+
944
+
945
+
946
+
947
+
948
+
949
+
950
+
951
+
952
+
953
+
954
+
955
+
956
+
957
+
958
+
959
+
960
+
961
+
962
+
963
+
964
+
965
+
966
+
967
+
968
+
969
+
970
+
971
+
972
+
973
+
974
+
975
+ <table><thead><tr><th>Name</th><th>Type</th><th>Meaning</th><th>Example</th></tr></thead><tbody><tr><td><code dir="auto">message</code></td><td>string</td><td>What failed, in one line: the fixed prefix <code dir="auto">Content validation failed: </code> followed by a comma-separated <code dir="auto">&#x3C;fieldId> (&#x3C;key>)</code> for each failing field, so the message alone is enough to log.</td><td><code dir="auto">Content validation failed: label (documentTypeForm.errors.required)</code></td></tr><tr><td><code dir="auto">fieldErrors</code></td><td>array</td><td>One entry per failing field. Empty for whole-record errors — an unknown document type, or a tree rule that forbids this placement; there the <code dir="auto">message</code> is that error’s own sentence instead.</td><td><code dir="auto">[…]</code></td></tr><tr><td><code dir="auto">fieldErrors[].fieldId</code></td><td>string</td><td>The field id that failed, matching a key in <code dir="auto">data</code>.</td><td><code dir="auto">label</code></td></tr><tr><td><code dir="auto">fieldErrors[].key</code></td><td>string</td><td>The rule that failed, as a translation key. One of <code dir="auto">documentTypeForm.errors.required</code>, <code dir="auto">.minLength</code>, <code dir="auto">.maxLength</code>, <code dir="auto">.pattern</code>, <code dir="auto">.min</code>, <code dir="auto">.max</code> — the same keys the admin editor’s own validator emits, so a client can feed them straight into its own translations.</td><td><code dir="auto">documentTypeForm.errors.required</code></td></tr><tr><td><code dir="auto">fieldErrors[].params</code></td><td>object, optional</td><td>The rule’s parameters, for building your own message. Present only for the four bounded rules.</td><td><code dir="auto">{"maxLength":120}</code></td></tr></tbody></table>
976
+ <p>The body looks like this — schema-derived:</p>
977
+ <div class="expressive-code"><figure class="frame not-content"><figcaption class="header"></figcaption><pre data-language="json"><code><div class="ec-line"><div class="code"><span style="--0:#D6DEEB;--1:#403F53">{</span></div></div><div class="ec-line"><div class="code"><span class="indent"> </span><span style="--0:#7FDBCA;--1:#096E72">"message"</span><span style="--0:#D6DEEB;--1:#403F53">: </span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#C789D6;--1:#7C5686">Content validation failed: label (documentTypeForm.errors.required)</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#D6DEEB;--1:#403F53">,</span></div></div><div class="ec-line"><div class="code"><span class="indent"> </span><span style="--0:#7FDBCA;--1:#096E72">"fieldErrors"</span><span style="--0:#D6DEEB;--1:#403F53">: [{ </span><span style="--0:#7FDBCA;--1:#096E72">"fieldId"</span><span style="--0:#D6DEEB;--1:#403F53">: </span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#C789D6;--1:#7C5686">label</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#D6DEEB;--1:#403F53">, </span><span style="--0:#7FDBCA;--1:#096E72">"key"</span><span style="--0:#D6DEEB;--1:#403F53">: </span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#C789D6;--1:#7C5686">documentTypeForm.errors.required</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#D6DEEB;--1:#403F53"> }]</span></div></div><div class="ec-line"><div class="code"><span style="--0:#D6DEEB;--1:#403F53">}</span></div></div></code></pre><div class="copy"><div aria-live="polite"></div><button title="Copy to clipboard" data-copied="Copied!" data-code="{ &#x22;message&#x22;: &#x22;Content validation failed: label (documentTypeForm.errors.required)&#x22;, &#x22;fieldErrors&#x22;: [{ &#x22;fieldId&#x22;: &#x22;label&#x22;, &#x22;key&#x22;: &#x22;documentTypeForm.errors.required&#x22; }]}"><div></div></button></div></figure></div>
978
+ <div class="sl-heading-wrapper level-h3"><h3 id="post-apiv1contentidpublish--make-it-readable"><code dir="auto">POST /api/v1/content/:id/publish</code> — make it readable</h3><a class="sl-anchor-link" href="#post-apiv1contentidpublish--make-it-readable"><span aria-hidden="true" class="sl-anchor-icon"><svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor"><path d="m12.11 15.39-3.88 3.88a2.52 2.52 0 0 1-3.5 0 2.47 2.47 0 0 1 0-3.5l3.88-3.88a1 1 0 1 0-1.42-1.42l-3.88 3.89a4.48 4.48 0 0 0 6.33 6.33l3.89-3.88a1 1 0 0 0-1.42-1.42m8.58-12.08a4.49 4.49 0 0 0-6.33 0l-3.89 3.88a1 1 0 1 0 1.42 1.42l3.88-3.88a2.52 2.52 0 0 1 3.5 0 2.47 2.47 0 0 1 0 3.5l-3.88 3.88a1 1 0 0 0 0 1.42 1 1 0 0 0 1.42 0l3.88-3.89a4.49 4.49 0 0 0 0-6.33M8.83 15.17a1 1 0 0 0 .71.29 1 1 0 0 0 .71-.29l4.92-4.92a1 1 0 1 0-1.42-1.42l-4.92 4.92a1 1 0 0 0 0 1.42"></path></svg></span><span class="sr-only" data-pagefind-ignore="">Section titled “POST /api/v1/content/:id/publish — make it readable”</span></a></div>
979
+ <p>Publishes the node’s latest saved version by repointing the current-version
980
+ pointer. No new version row is created, and nothing is deleted — the
981
+ previous current version stays in history.</p>
982
+
983
+
984
+
985
+
986
+
987
+
988
+
989
+
990
+
991
+
992
+
993
+
994
+
995
+
996
+
997
+
998
+
999
+
1000
+
1001
+
1002
+
1003
+
1004
+
1005
+ <table><thead><tr><th>Name</th><th>Type</th><th>Meaning</th><th>Example</th></tr></thead><tbody><tr><td><code dir="auto">:id</code></td><td>string (uuid), path parameter</td><td>The node to publish. Unknown = <code dir="auto">404</code> with the uniform read message.</td><td><code dir="auto">b7c91f42-3d18-4a67-9e05-1c8f2b6d4a30</code></td></tr><tr><td><em>(no body)</em></td><td>—</td><td>Nothing to send.</td><td>—</td></tr></tbody></table>
1006
+ <p>Publishing the node:</p>
1007
+ <div class="expressive-code"><figure class="frame is-terminal not-content"><figcaption class="header"><span class="title"></span><span class="sr-only">Terminal window</span></figcaption><pre data-language="sh"><code><div class="ec-line"><div class="code"><span style="--0:#82AAFF;--1:#3B61B0">curl</span><span style="--0:#D6DEEB;--1:#403F53"> </span><span style="--0:#82AAFF;--1:#3B61B0">-s</span><span style="--0:#D6DEEB;--1:#403F53"> </span><span style="--0:#82AAFF;--1:#3B61B0">-X</span><span style="--0:#D6DEEB;--1:#403F53"> </span><span style="--0:#ECC48D;--1:#3B61B0">POST</span><span style="--0:#D6DEEB;--1:#403F53"> </span><span style="--0:#F78C6C;--1:#AA0982">\</span></div></div><div class="ec-line"><div class="code"><span class="indent"> </span><span style="--0:#ECC48D;--1:#3B61B0">http://localhost:3000/api/v1/content/b7c91f42-3d18-4a67-9e05-1c8f2b6d4a30/publish</span><span style="--0:#D6DEEB;--1:#403F53"> </span><span style="--0:#F78C6C;--1:#AA0982">\</span></div></div><div class="ec-line"><div class="code"><span class="indent"> </span><span style="--0:#82AAFF;--1:#3B61B0">-H</span><span style="--0:#D6DEEB;--1:#403F53"> </span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#ECC48D;--1:#984E4D">Authorization: Bearer </span><span style="--0:#C5E478;--1:#3B61B0">$VELORA_API_KEY</span><span style="--0:#D9F5DD;--1:#111111">"</span></div></div></code></pre><div class="copy"><div aria-live="polite"></div><button title="Copy to clipboard" data-copied="Copied!" data-code="curl -s -X POST \ http://localhost:3000/api/v1/content/b7c91f42-3d18-4a67-9e05-1c8f2b6d4a30/publish \ -H &#x22;Authorization: Bearer $VELORA_API_KEY&#x22;"><div></div></button></div></figure></div>
1008
+ <p>A success is <code dir="auto">200</code> with the write-response shape, now carrying a
1009
+ <code dir="auto">publishedAt</code> — from this moment the node answers on the read routes:</p>
1010
+ <div class="expressive-code"><figure class="frame not-content"><figcaption class="header"></figcaption><pre data-language="json"><code><div class="ec-line"><div class="code"><span style="--0:#D6DEEB;--1:#403F53">{</span></div></div><div class="ec-line"><div class="code"><span class="indent"> </span><span style="--0:#7FDBCA;--1:#096E72">"id"</span><span style="--0:#D6DEEB;--1:#403F53">: </span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#C789D6;--1:#7C5686">b7c91f42-3d18-4a67-9e05-1c8f2b6d4a30</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#D6DEEB;--1:#403F53">,</span></div></div><div class="ec-line"><div class="code"><span class="indent"> </span><span style="--0:#7FDBCA;--1:#096E72">"documentTypeId"</span><span style="--0:#D6DEEB;--1:#403F53">: </span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#C789D6;--1:#7C5686">d90012af-c528-4cff-b0c9-cc4c9cfc05c9</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#D6DEEB;--1:#403F53">,</span></div></div><div class="ec-line"><div class="code"><span class="indent"> </span><span style="--0:#7FDBCA;--1:#096E72">"parentId"</span><span style="--0:#D6DEEB;--1:#403F53">: </span><span style="--0:#FF6A83;--1:#A24848">null</span><span style="--0:#D6DEEB;--1:#403F53">,</span></div></div><div class="ec-line"><div class="code"><span class="indent"> </span><span style="--0:#7FDBCA;--1:#096E72">"path"</span><span style="--0:#D6DEEB;--1:#403F53">: </span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#C789D6;--1:#7C5686">/hello-world</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#D6DEEB;--1:#403F53">,</span></div></div><div class="ec-line"><div class="code"><span class="indent"> </span><span style="--0:#7FDBCA;--1:#096E72">"locale"</span><span style="--0:#D6DEEB;--1:#403F53">: </span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#C789D6;--1:#7C5686">en</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#D6DEEB;--1:#403F53">,</span></div></div><div class="ec-line"><div class="code"><span class="indent"> </span><span style="--0:#7FDBCA;--1:#096E72">"version"</span><span style="--0:#D6DEEB;--1:#403F53">: </span><span style="--0:#F78C6C;--1:#AA0982">3</span><span style="--0:#D6DEEB;--1:#403F53">,</span></div></div><div class="ec-line"><div class="code"><span class="indent"> </span><span style="--0:#7FDBCA;--1:#096E72">"publishedAt"</span><span style="--0:#D6DEEB;--1:#403F53">: </span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#C789D6;--1:#7C5686">2026-08-31T09:41:22.180Z</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#D6DEEB;--1:#403F53">,</span></div></div><div class="ec-line"><div class="code"><span class="indent"> </span><span style="--0:#7FDBCA;--1:#096E72">"data"</span><span style="--0:#D6DEEB;--1:#403F53">: { </span><span style="--0:#7FDBCA;--1:#096E72">"label"</span><span style="--0:#D6DEEB;--1:#403F53">: </span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#C789D6;--1:#7C5686">Hello, world</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#D6DEEB;--1:#403F53">, </span><span style="--0:#7FDBCA;--1:#096E72">"description"</span><span style="--0:#D6DEEB;--1:#403F53">: </span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#C789D6;--1:#7C5686">Edited.</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#D6DEEB;--1:#403F53"> }</span></div></div><div class="ec-line"><div class="code"><span style="--0:#D6DEEB;--1:#403F53">}</span></div></div></code></pre><div class="copy"><div aria-live="polite"></div><button title="Copy to clipboard" data-copied="Copied!" data-code="{ &#x22;id&#x22;: &#x22;b7c91f42-3d18-4a67-9e05-1c8f2b6d4a30&#x22;, &#x22;documentTypeId&#x22;: &#x22;d90012af-c528-4cff-b0c9-cc4c9cfc05c9&#x22;, &#x22;parentId&#x22;: null, &#x22;path&#x22;: &#x22;/hello-world&#x22;, &#x22;locale&#x22;: &#x22;en&#x22;, &#x22;version&#x22;: 3, &#x22;publishedAt&#x22;: &#x22;2026-08-31T09:41:22.180Z&#x22;, &#x22;data&#x22;: { &#x22;label&#x22;: &#x22;Hello, world&#x22;, &#x22;description&#x22;: &#x22;Edited.&#x22; }}"><div></div></button></div></figure></div>
1011
+ <div class="sl-heading-wrapper level-h2"><h2 id="document-types">Document types</h2><a class="sl-anchor-link" href="#document-types"><span aria-hidden="true" class="sl-anchor-icon"><svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor"><path d="m12.11 15.39-3.88 3.88a2.52 2.52 0 0 1-3.5 0 2.47 2.47 0 0 1 0-3.5l3.88-3.88a1 1 0 1 0-1.42-1.42l-3.88 3.89a4.48 4.48 0 0 0 6.33 6.33l3.89-3.88a1 1 0 0 0-1.42-1.42m8.58-12.08a4.49 4.49 0 0 0-6.33 0l-3.89 3.88a1 1 0 1 0 1.42 1.42l3.88-3.88a2.52 2.52 0 0 1 3.5 0 2.47 2.47 0 0 1 0 3.5l-3.88 3.88a1 1 0 0 0 0 1.42 1 1 0 0 0 1.42 0l3.88-3.89a4.49 4.49 0 0 0 0-6.33M8.83 15.17a1 1 0 0 0 .71.29 1 1 0 0 0 .71-.29l4.92-4.92a1 1 0 1 0-1.42-1.42l-4.92 4.92a1 1 0 0 0 0 1.42"></path></svg></span><span class="sr-only" data-pagefind-ignore="">Section titled “Document types”</span></a></div>
1012
+ <p>Field values arrive keyed by field id, so a consumer that wants labels,
1013
+ data types or validation rules reads them from the document type. This is
1014
+ the introspection surface for interpreting <code dir="auto">data</code>.</p>
1015
+ <div class="sl-heading-wrapper level-h3"><h3 id="get-apiv1document-types"><code dir="auto">GET /api/v1/document-types</code></h3><a class="sl-anchor-link" href="#get-apiv1document-types"><span aria-hidden="true" class="sl-anchor-icon"><svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor"><path d="m12.11 15.39-3.88 3.88a2.52 2.52 0 0 1-3.5 0 2.47 2.47 0 0 1 0-3.5l3.88-3.88a1 1 0 1 0-1.42-1.42l-3.88 3.89a4.48 4.48 0 0 0 6.33 6.33l3.89-3.88a1 1 0 0 0-1.42-1.42m8.58-12.08a4.49 4.49 0 0 0-6.33 0l-3.89 3.88a1 1 0 1 0 1.42 1.42l3.88-3.88a2.52 2.52 0 0 1 3.5 0 2.47 2.47 0 0 1 0 3.5l-3.88 3.88a1 1 0 0 0 0 1.42 1 1 0 0 0 1.42 0l3.88-3.89a4.49 4.49 0 0 0 0-6.33M8.83 15.17a1 1 0 0 0 .71.29 1 1 0 0 0 .71-.29l4.92-4.92a1 1 0 1 0-1.42-1.42l-4.92 4.92a1 1 0 0 0 0 1.42"></path></svg></span><span class="sr-only" data-pagefind-ignore="">Section titled “GET /api/v1/document-types”</span></a></div>
1016
+
1017
+
1018
+
1019
+
1020
+
1021
+
1022
+
1023
+
1024
+
1025
+
1026
+
1027
+
1028
+
1029
+
1030
+
1031
+
1032
+
1033
+
1034
+
1035
+
1036
+
1037
+
1038
+
1039
+ <table><thead><tr><th>Name</th><th>Type</th><th>Meaning</th><th>Example</th></tr></thead><tbody><tr><td><code dir="auto">cursor</code></td><td>string (uuid), optional</td><td>Pagination cursor.</td><td><code dir="auto">f0491ccf-0511-43a7-862b-fdf9dce55f3c</code></td></tr><tr><td><code dir="auto">limit</code></td><td>integer 1–100</td><td>Page size, default <code dir="auto">50</code>.</td><td><code dir="auto">3</code></td></tr></tbody></table>
1040
+ <p>Listing three of the demo’s types:</p>
1041
+ <div class="expressive-code"><figure class="frame is-terminal not-content"><figcaption class="header"><span class="title"></span><span class="sr-only">Terminal window</span></figcaption><pre data-language="sh"><code><div class="ec-line"><div class="code"><span style="--0:#82AAFF;--1:#3B61B0">curl</span><span style="--0:#D6DEEB;--1:#403F53"> </span><span style="--0:#82AAFF;--1:#3B61B0">-s</span><span style="--0:#D6DEEB;--1:#403F53"> </span><span style="--0:#82AAFF;--1:#3B61B0">-H</span><span style="--0:#D6DEEB;--1:#403F53"> </span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#ECC48D;--1:#984E4D">Authorization: Bearer vk_4ac0…9328</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#D6DEEB;--1:#403F53"> </span><span style="--0:#F78C6C;--1:#AA0982">\</span></div></div><div class="ec-line"><div class="code"><span class="indent"> </span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#ECC48D;--1:#984E4D">https://demo.velora-cms.com/api/v1/document-types?limit=3</span><span style="--0:#D9F5DD;--1:#111111">"</span></div></div></code></pre><div class="copy"><div aria-live="polite"></div><button title="Copy to clipboard" data-copied="Copied!" data-code="curl -s -H &#x22;Authorization: Bearer vk_4ac0…9328&#x22; \ &#x22;https://demo.velora-cms.com/api/v1/document-types?limit=3&#x22;"><div></div></button></div></figure></div>
1042
+ <p>Each entry carries its fields inline — <strong>live</strong>, trimmed here to the first
1043
+ of the three for readability (the full response also contains <code dir="auto">Tag</code> and
1044
+ <code dir="auto">Post</code>):</p>
1045
+ <div class="expressive-code"><figure class="frame not-content"><figcaption class="header"></figcaption><pre data-language="json"><code><div class="ec-line"><div class="code"><span style="--0:#D6DEEB;--1:#403F53">{</span><span style="--0:#7FDBCA;--1:#096E72">"items"</span><span style="--0:#D6DEEB;--1:#403F53">:[{</span><span style="--0:#7FDBCA;--1:#096E72">"id"</span><span style="--0:#D6DEEB;--1:#403F53">:</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#C789D6;--1:#7C5686">511011a7-2a0b-42be-95c1-f6831d7dcbe6</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#D6DEEB;--1:#403F53">,</span><span style="--0:#7FDBCA;--1:#096E72">"name"</span><span style="--0:#D6DEEB;--1:#403F53">:</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#C789D6;--1:#7C5686">Author</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#D6DEEB;--1:#403F53">,</span><span style="--0:#7FDBCA;--1:#096E72">"icon"</span><span style="--0:#D6DEEB;--1:#403F53">:</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#C789D6;--1:#7C5686">user</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#D6DEEB;--1:#403F53">,</span><span style="--0:#7FDBCA;--1:#096E72">"kind"</span><span style="--0:#D6DEEB;--1:#403F53">:</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#C789D6;--1:#7C5686">page</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#D6DEEB;--1:#403F53">,</span><span style="--0:#7FDBCA;--1:#096E72">"fields"</span><span style="--0:#D6DEEB;--1:#403F53">:[{</span><span style="--0:#7FDBCA;--1:#096E72">"id"</span><span style="--0:#D6DEEB;--1:#403F53">:</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#C789D6;--1:#7C5686">name</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#D6DEEB;--1:#403F53">,</span><span style="--0:#7FDBCA;--1:#096E72">"name"</span><span style="--0:#D6DEEB;--1:#403F53">:</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#C789D6;--1:#7C5686">Name</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#D6DEEB;--1:#403F53">,</span><span style="--0:#7FDBCA;--1:#096E72">"datatypeId"</span><span style="--0:#D6DEEB;--1:#403F53">:</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#C789D6;--1:#7C5686">b1e00000-0000-4000-8000-000000000001</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#D6DEEB;--1:#403F53">,</span><span style="--0:#7FDBCA;--1:#096E72">"validation"</span><span style="--0:#D6DEEB;--1:#403F53">:{</span><span style="--0:#7FDBCA;--1:#096E72">"required"</span><span style="--0:#D6DEEB;--1:#403F53">:</span><span style="--0:#FF6A83;--1:#A24848">true</span><span style="--0:#D6DEEB;--1:#403F53">}},{</span><span style="--0:#7FDBCA;--1:#096E72">"id"</span><span style="--0:#D6DEEB;--1:#403F53">:</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#C789D6;--1:#7C5686">bio</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#D6DEEB;--1:#403F53">,</span><span style="--0:#7FDBCA;--1:#096E72">"name"</span><span style="--0:#D6DEEB;--1:#403F53">:</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#C789D6;--1:#7C5686">Bio</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#D6DEEB;--1:#403F53">,</span><span style="--0:#7FDBCA;--1:#096E72">"datatypeId"</span><span style="--0:#D6DEEB;--1:#403F53">:</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#C789D6;--1:#7C5686">b1e00000-0000-4000-8000-000000000006</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#D6DEEB;--1:#403F53">},{</span><span style="--0:#7FDBCA;--1:#096E72">"id"</span><span style="--0:#D6DEEB;--1:#403F53">:</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#C789D6;--1:#7C5686">avatar</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#D6DEEB;--1:#403F53">,</span><span style="--0:#7FDBCA;--1:#096E72">"name"</span><span style="--0:#D6DEEB;--1:#403F53">:</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#C789D6;--1:#7C5686">Avatar</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#D6DEEB;--1:#403F53">,</span><span style="--0:#7FDBCA;--1:#096E72">"datatypeId"</span><span style="--0:#D6DEEB;--1:#403F53">:</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#C789D6;--1:#7C5686">b1e00000-0000-4000-8000-000000000005</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#D6DEEB;--1:#403F53">}]}],</span><span style="--0:#7FDBCA;--1:#096E72">"nextCursor"</span><span style="--0:#D6DEEB;--1:#403F53">:</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#C789D6;--1:#7C5686">f0491ccf-0511-43a7-862b-fdf9dce55f3c</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#D6DEEB;--1:#403F53">}</span></div></div></code></pre><div class="copy"><div aria-live="polite"></div><button title="Copy to clipboard" data-copied="Copied!" data-code="{&#x22;items&#x22;:[{&#x22;id&#x22;:&#x22;511011a7-2a0b-42be-95c1-f6831d7dcbe6&#x22;,&#x22;name&#x22;:&#x22;Author&#x22;,&#x22;icon&#x22;:&#x22;user&#x22;,&#x22;kind&#x22;:&#x22;page&#x22;,&#x22;fields&#x22;:[{&#x22;id&#x22;:&#x22;name&#x22;,&#x22;name&#x22;:&#x22;Name&#x22;,&#x22;datatypeId&#x22;:&#x22;b1e00000-0000-4000-8000-000000000001&#x22;,&#x22;validation&#x22;:{&#x22;required&#x22;:true}},{&#x22;id&#x22;:&#x22;bio&#x22;,&#x22;name&#x22;:&#x22;Bio&#x22;,&#x22;datatypeId&#x22;:&#x22;b1e00000-0000-4000-8000-000000000006&#x22;},{&#x22;id&#x22;:&#x22;avatar&#x22;,&#x22;name&#x22;:&#x22;Avatar&#x22;,&#x22;datatypeId&#x22;:&#x22;b1e00000-0000-4000-8000-000000000005&#x22;}]}],&#x22;nextCursor&#x22;:&#x22;f0491ccf-0511-43a7-862b-fdf9dce55f3c&#x22;}"><div></div></button></div></figure></div>
1046
+ <div class="sl-heading-wrapper level-h3"><h3 id="get-apiv1document-typesid"><code dir="auto">GET /api/v1/document-types/:id</code></h3><a class="sl-anchor-link" href="#get-apiv1document-typesid"><span aria-hidden="true" class="sl-anchor-icon"><svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor"><path d="m12.11 15.39-3.88 3.88a2.52 2.52 0 0 1-3.5 0 2.47 2.47 0 0 1 0-3.5l3.88-3.88a1 1 0 1 0-1.42-1.42l-3.88 3.89a4.48 4.48 0 0 0 6.33 6.33l3.89-3.88a1 1 0 0 0-1.42-1.42m8.58-12.08a4.49 4.49 0 0 0-6.33 0l-3.89 3.88a1 1 0 1 0 1.42 1.42l3.88-3.88a2.52 2.52 0 0 1 3.5 0 2.47 2.47 0 0 1 0 3.5l-3.88 3.88a1 1 0 0 0 0 1.42 1 1 0 0 0 1.42 0l3.88-3.89a4.49 4.49 0 0 0 0-6.33M8.83 15.17a1 1 0 0 0 .71.29 1 1 0 0 0 .71-.29l4.92-4.92a1 1 0 1 0-1.42-1.42l-4.92 4.92a1 1 0 0 0 0 1.42"></path></svg></span><span class="sr-only" data-pagefind-ignore="">Section titled “GET /api/v1/document-types/:id”</span></a></div>
1047
+ <p>One type by id; unknown is a <code dir="auto">404</code> with
1048
+ <code dir="auto">{"message":"No document type found for id &#x3C;id>"}</code>.</p>
1049
+
1050
+
1051
+
1052
+
1053
+
1054
+
1055
+
1056
+
1057
+
1058
+
1059
+
1060
+
1061
+
1062
+
1063
+
1064
+
1065
+
1066
+ <table><thead><tr><th>Name</th><th>Type</th><th>Meaning</th><th>Example</th></tr></thead><tbody><tr><td><code dir="auto">:id</code></td><td>string (uuid), path parameter</td><td>The document type’s identity.</td><td><code dir="auto">f0491ccf-0511-43a7-862b-fdf9dce55f3c</code></td></tr></tbody></table>
1067
+ <div class="expressive-code"><figure class="frame is-terminal not-content"><figcaption class="header"><span class="title"></span><span class="sr-only">Terminal window</span></figcaption><pre data-language="sh"><code><div class="ec-line"><div class="code"><span style="--0:#82AAFF;--1:#3B61B0">curl</span><span style="--0:#D6DEEB;--1:#403F53"> </span><span style="--0:#82AAFF;--1:#3B61B0">-s</span><span style="--0:#D6DEEB;--1:#403F53"> </span><span style="--0:#82AAFF;--1:#3B61B0">-H</span><span style="--0:#D6DEEB;--1:#403F53"> </span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#ECC48D;--1:#984E4D">Authorization: Bearer vk_4ac0…9328</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#D6DEEB;--1:#403F53"> </span><span style="--0:#F78C6C;--1:#AA0982">\</span></div></div><div class="ec-line"><div class="code"><span class="indent"> </span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#ECC48D;--1:#984E4D">https://demo.velora-cms.com/api/v1/document-types/f0491ccf-0511-43a7-862b-fdf9dce55f3c</span><span style="--0:#D9F5DD;--1:#111111">"</span></div></div></code></pre><div class="copy"><div aria-live="polite"></div><button title="Copy to clipboard" data-copied="Copied!" data-code="curl -s -H &#x22;Authorization: Bearer vk_4ac0…9328&#x22; \ &#x22;https://demo.velora-cms.com/api/v1/document-types/f0491ccf-0511-43a7-862b-fdf9dce55f3c&#x22;"><div></div></button></div></figure></div>
1068
+ <p>The demo’s <code dir="auto">Post</code> type — <strong>live</strong>. Its <code dir="auto">seo</code> field’s <code dir="auto">datatypeId</code> is a
1069
+ plugin-provided data type, which is what a plugin-contributed field looks
1070
+ like from outside:</p>
1071
+ <div class="expressive-code"><figure class="frame not-content"><figcaption class="header"></figcaption><pre data-language="json"><code><div class="ec-line"><div class="code"><span style="--0:#D6DEEB;--1:#403F53">{</span><span style="--0:#7FDBCA;--1:#096E72">"id"</span><span style="--0:#D6DEEB;--1:#403F53">:</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#C789D6;--1:#7C5686">f0491ccf-0511-43a7-862b-fdf9dce55f3c</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#D6DEEB;--1:#403F53">,</span><span style="--0:#7FDBCA;--1:#096E72">"name"</span><span style="--0:#D6DEEB;--1:#403F53">:</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#C789D6;--1:#7C5686">Post</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#D6DEEB;--1:#403F53">,</span><span style="--0:#7FDBCA;--1:#096E72">"icon"</span><span style="--0:#D6DEEB;--1:#403F53">:</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#C789D6;--1:#7C5686">file-text</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#D6DEEB;--1:#403F53">,</span><span style="--0:#7FDBCA;--1:#096E72">"kind"</span><span style="--0:#D6DEEB;--1:#403F53">:</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#C789D6;--1:#7C5686">page</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#D6DEEB;--1:#403F53">,</span><span style="--0:#7FDBCA;--1:#096E72">"fields"</span><span style="--0:#D6DEEB;--1:#403F53">:[{</span><span style="--0:#7FDBCA;--1:#096E72">"id"</span><span style="--0:#D6DEEB;--1:#403F53">:</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#C789D6;--1:#7C5686">title</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#D6DEEB;--1:#403F53">,</span><span style="--0:#7FDBCA;--1:#096E72">"name"</span><span style="--0:#D6DEEB;--1:#403F53">:</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#C789D6;--1:#7C5686">Title</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#D6DEEB;--1:#403F53">,</span><span style="--0:#7FDBCA;--1:#096E72">"datatypeId"</span><span style="--0:#D6DEEB;--1:#403F53">:</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#C789D6;--1:#7C5686">b1e00000-0000-4000-8000-000000000001</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#D6DEEB;--1:#403F53">,</span><span style="--0:#7FDBCA;--1:#096E72">"validation"</span><span style="--0:#D6DEEB;--1:#403F53">:{</span><span style="--0:#7FDBCA;--1:#096E72">"required"</span><span style="--0:#D6DEEB;--1:#403F53">:</span><span style="--0:#FF6A83;--1:#A24848">true</span><span style="--0:#D6DEEB;--1:#403F53">}},{</span><span style="--0:#7FDBCA;--1:#096E72">"id"</span><span style="--0:#D6DEEB;--1:#403F53">:</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#C789D6;--1:#7C5686">body</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#D6DEEB;--1:#403F53">,</span><span style="--0:#7FDBCA;--1:#096E72">"name"</span><span style="--0:#D6DEEB;--1:#403F53">:</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#C789D6;--1:#7C5686">Body</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#D6DEEB;--1:#403F53">,</span><span style="--0:#7FDBCA;--1:#096E72">"datatypeId"</span><span style="--0:#D6DEEB;--1:#403F53">:</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#C789D6;--1:#7C5686">b1e00000-0000-4000-8000-000000000006</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#D6DEEB;--1:#403F53">,</span><span style="--0:#7FDBCA;--1:#096E72">"validation"</span><span style="--0:#D6DEEB;--1:#403F53">:{</span><span style="--0:#7FDBCA;--1:#096E72">"required"</span><span style="--0:#D6DEEB;--1:#403F53">:</span><span style="--0:#FF6A83;--1:#A24848">true</span><span style="--0:#D6DEEB;--1:#403F53">}},{</span><span style="--0:#7FDBCA;--1:#096E72">"id"</span><span style="--0:#D6DEEB;--1:#403F53">:</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#C789D6;--1:#7C5686">featuredImage</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#D6DEEB;--1:#403F53">,</span><span style="--0:#7FDBCA;--1:#096E72">"name"</span><span style="--0:#D6DEEB;--1:#403F53">:</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#C789D6;--1:#7C5686">Featured Image</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#D6DEEB;--1:#403F53">,</span><span style="--0:#7FDBCA;--1:#096E72">"datatypeId"</span><span style="--0:#D6DEEB;--1:#403F53">:</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#C789D6;--1:#7C5686">b1e00000-0000-4000-8000-000000000005</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#D6DEEB;--1:#403F53">},{</span><span style="--0:#7FDBCA;--1:#096E72">"id"</span><span style="--0:#D6DEEB;--1:#403F53">:</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#C789D6;--1:#7C5686">publishedDate</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#D6DEEB;--1:#403F53">,</span><span style="--0:#7FDBCA;--1:#096E72">"name"</span><span style="--0:#D6DEEB;--1:#403F53">:</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#C789D6;--1:#7C5686">Published Date</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#D6DEEB;--1:#403F53">,</span><span style="--0:#7FDBCA;--1:#096E72">"datatypeId"</span><span style="--0:#D6DEEB;--1:#403F53">:</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#C789D6;--1:#7C5686">b1e00000-0000-4000-8000-000000000004</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#D6DEEB;--1:#403F53">},{</span><span style="--0:#7FDBCA;--1:#096E72">"id"</span><span style="--0:#D6DEEB;--1:#403F53">:</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#C789D6;--1:#7C5686">tags</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#D6DEEB;--1:#403F53">,</span><span style="--0:#7FDBCA;--1:#096E72">"name"</span><span style="--0:#D6DEEB;--1:#403F53">:</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#C789D6;--1:#7C5686">Tags (comma-separated slugs, e.g. </span><span style="--0:#F78C6C;--1:#AA0982">\"</span><span style="--0:#C789D6;--1:#7C5686">guides,announcements</span><span style="--0:#F78C6C;--1:#AA0982">\"</span><span style="--0:#C789D6;--1:#7C5686">)</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#D6DEEB;--1:#403F53">,</span><span style="--0:#7FDBCA;--1:#096E72">"datatypeId"</span><span style="--0:#D6DEEB;--1:#403F53">:</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#C789D6;--1:#7C5686">b1e00000-0000-4000-8000-000000000001</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#D6DEEB;--1:#403F53">},{</span><span style="--0:#7FDBCA;--1:#096E72">"id"</span><span style="--0:#D6DEEB;--1:#403F53">:</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#C789D6;--1:#7C5686">seo</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#D6DEEB;--1:#403F53">,</span><span style="--0:#7FDBCA;--1:#096E72">"name"</span><span style="--0:#D6DEEB;--1:#403F53">:</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#C789D6;--1:#7C5686">SEO</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#D6DEEB;--1:#403F53">,</span><span style="--0:#7FDBCA;--1:#096E72">"datatypeId"</span><span style="--0:#D6DEEB;--1:#403F53">:</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#C789D6;--1:#7C5686">62b30a9d-e0b6-4189-8aa9-7371c8cd9c63</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#D6DEEB;--1:#403F53">}]}</span></div></div></code></pre><div class="copy"><div aria-live="polite"></div><button title="Copy to clipboard" data-copied="Copied!" data-code="{&#x22;id&#x22;:&#x22;f0491ccf-0511-43a7-862b-fdf9dce55f3c&#x22;,&#x22;name&#x22;:&#x22;Post&#x22;,&#x22;icon&#x22;:&#x22;file-text&#x22;,&#x22;kind&#x22;:&#x22;page&#x22;,&#x22;fields&#x22;:[{&#x22;id&#x22;:&#x22;title&#x22;,&#x22;name&#x22;:&#x22;Title&#x22;,&#x22;datatypeId&#x22;:&#x22;b1e00000-0000-4000-8000-000000000001&#x22;,&#x22;validation&#x22;:{&#x22;required&#x22;:true}},{&#x22;id&#x22;:&#x22;body&#x22;,&#x22;name&#x22;:&#x22;Body&#x22;,&#x22;datatypeId&#x22;:&#x22;b1e00000-0000-4000-8000-000000000006&#x22;,&#x22;validation&#x22;:{&#x22;required&#x22;:true}},{&#x22;id&#x22;:&#x22;featuredImage&#x22;,&#x22;name&#x22;:&#x22;Featured Image&#x22;,&#x22;datatypeId&#x22;:&#x22;b1e00000-0000-4000-8000-000000000005&#x22;},{&#x22;id&#x22;:&#x22;publishedDate&#x22;,&#x22;name&#x22;:&#x22;Published Date&#x22;,&#x22;datatypeId&#x22;:&#x22;b1e00000-0000-4000-8000-000000000004&#x22;},{&#x22;id&#x22;:&#x22;tags&#x22;,&#x22;name&#x22;:&#x22;Tags (comma-separated slugs, e.g. \&#x22;guides,announcements\&#x22;)&#x22;,&#x22;datatypeId&#x22;:&#x22;b1e00000-0000-4000-8000-000000000001&#x22;},{&#x22;id&#x22;:&#x22;seo&#x22;,&#x22;name&#x22;:&#x22;SEO&#x22;,&#x22;datatypeId&#x22;:&#x22;62b30a9d-e0b6-4189-8aa9-7371c8cd9c63&#x22;}]}"><div></div></button></div></figure></div>
1072
+ <p>Every property of a document type on this surface:</p>
1073
+
1074
+
1075
+
1076
+
1077
+
1078
+
1079
+
1080
+
1081
+
1082
+
1083
+
1084
+
1085
+
1086
+
1087
+
1088
+
1089
+
1090
+
1091
+
1092
+
1093
+
1094
+
1095
+
1096
+
1097
+
1098
+
1099
+
1100
+
1101
+
1102
+
1103
+
1104
+
1105
+
1106
+
1107
+
1108
+
1109
+
1110
+
1111
+
1112
+
1113
+
1114
+
1115
+
1116
+
1117
+
1118
+
1119
+
1120
+
1121
+
1122
+
1123
+
1124
+
1125
+
1126
+
1127
+
1128
+
1129
+
1130
+
1131
+
1132
+
1133
+
1134
+
1135
+
1136
+
1137
+
1138
+ <table><thead><tr><th>Name</th><th>Type</th><th>Meaning</th><th>Example</th></tr></thead><tbody><tr><td><code dir="auto">id</code></td><td>string (uuid)</td><td>The type’s identity — the <code dir="auto">documentTypeId</code> on every content row.</td><td><code dir="auto">f0491ccf-0511-43a7-862b-fdf9dce55f3c</code></td></tr><tr><td><code dir="auto">name</code></td><td>string, non-empty</td><td>Display name, and the stable handle a frontend should match on rather than hardcoding an id (ids are minted per instance).</td><td><code dir="auto">Post</code></td></tr><tr><td><code dir="auto">icon</code></td><td>string, non-empty</td><td>Icon name the admin renders.</td><td><code dir="auto">file-text</code></td></tr><tr><td><code dir="auto">kind</code></td><td><code dir="auto">"page"</code> or <code dir="auto">"element"</code></td><td>Whether nodes of this type live in the content tree (<code dir="auto">page</code>) or are embedded pieces (<code dir="auto">element</code>).</td><td><code dir="auto">page</code></td></tr><tr><td><code dir="auto">fields</code></td><td>array</td><td>The field definitions, in order.</td><td><code dir="auto">[…]</code></td></tr><tr><td><code dir="auto">fields[].id</code></td><td>string</td><td>The <strong>data key</strong> — this is the key in an item’s <code dir="auto">data</code>.</td><td><code dir="auto">title</code></td></tr><tr><td><code dir="auto">fields[].name</code></td><td>string, non-empty</td><td>Human label for the field.</td><td><code dir="auto">Title</code></td></tr><tr><td><code dir="auto">fields[].datatypeId</code></td><td>string, non-empty</td><td>Which data type stores the value; built-in ids start <code dir="auto">b1e00000-…</code>, plugin-provided ones are minted per install.</td><td><code dir="auto">b1e00000-0000-4000-8000-000000000001</code></td></tr><tr><td><code dir="auto">fields[].validation</code></td><td>object, optional</td><td>The client-facing rules: <code dir="auto">required</code>, <code dir="auto">minLength</code>, <code dir="auto">maxLength</code>, <code dir="auto">min</code>, <code dir="auto">max</code>, <code dir="auto">pattern</code>. Absent when the field has none.</td><td><code dir="auto">{"required":true}</code></td></tr></tbody></table>
1139
+ <p>The public projection is deliberately narrower than the admin one: tabs,
1140
+ tree rules and the default template id are admin concerns and are not
1141
+ exposed here. See <a href="/docs/v1/using-velora/content-model/">Content model</a> for
1142
+ those.</p>
1143
+ <div class="sl-heading-wrapper level-h2"><h2 id="media">Media</h2><a class="sl-anchor-link" href="#media"><span aria-hidden="true" class="sl-anchor-icon"><svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor"><path d="m12.11 15.39-3.88 3.88a2.52 2.52 0 0 1-3.5 0 2.47 2.47 0 0 1 0-3.5l3.88-3.88a1 1 0 1 0-1.42-1.42l-3.88 3.89a4.48 4.48 0 0 0 6.33 6.33l3.89-3.88a1 1 0 0 0-1.42-1.42m8.58-12.08a4.49 4.49 0 0 0-6.33 0l-3.89 3.88a1 1 0 1 0 1.42 1.42l3.88-3.88a2.52 2.52 0 0 1 3.5 0 2.47 2.47 0 0 1 0 3.5l-3.88 3.88a1 1 0 0 0 0 1.42 1 1 0 0 0 1.42 0l3.88-3.89a4.49 4.49 0 0 0 0-6.33M8.83 15.17a1 1 0 0 0 .71.29 1 1 0 0 0 .71-.29l4.92-4.92a1 1 0 1 0-1.42-1.42l-4.92 4.92a1 1 0 0 0 0 1.42"></path></svg></span><span class="sr-only" data-pagefind-ignore="">Section titled “Media”</span></a></div>
1144
+ <div class="sl-heading-wrapper level-h3"><h3 id="get-apiv1media"><code dir="auto">GET /api/v1/media</code></h3><a class="sl-anchor-link" href="#get-apiv1media"><span aria-hidden="true" class="sl-anchor-icon"><svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor"><path d="m12.11 15.39-3.88 3.88a2.52 2.52 0 0 1-3.5 0 2.47 2.47 0 0 1 0-3.5l3.88-3.88a1 1 0 1 0-1.42-1.42l-3.88 3.89a4.48 4.48 0 0 0 6.33 6.33l3.89-3.88a1 1 0 0 0-1.42-1.42m8.58-12.08a4.49 4.49 0 0 0-6.33 0l-3.89 3.88a1 1 0 1 0 1.42 1.42l3.88-3.88a2.52 2.52 0 0 1 3.5 0 2.47 2.47 0 0 1 0 3.5l-3.88 3.88a1 1 0 0 0 0 1.42 1 1 0 0 0 1.42 0l3.88-3.89a4.49 4.49 0 0 0 0-6.33M8.83 15.17a1 1 0 0 0 .71.29 1 1 0 0 0 .71-.29l4.92-4.92a1 1 0 1 0-1.42-1.42l-4.92 4.92a1 1 0 0 0 0 1.42"></path></svg></span><span class="sr-only" data-pagefind-ignore="">Section titled “GET /api/v1/media”</span></a></div>
1145
+
1146
+
1147
+
1148
+
1149
+
1150
+
1151
+
1152
+
1153
+
1154
+
1155
+
1156
+
1157
+
1158
+
1159
+
1160
+
1161
+
1162
+
1163
+
1164
+
1165
+
1166
+
1167
+
1168
+
1169
+
1170
+
1171
+
1172
+
1173
+
1174
+ <table><thead><tr><th>Name</th><th>Type</th><th>Meaning</th><th>Example</th></tr></thead><tbody><tr><td><code dir="auto">cursor</code></td><td>string (uuid), optional</td><td>Pagination cursor.</td><td><code dir="auto">9f1d3b7c-52ae-4d08-9b31-6c74a0f5e218</code></td></tr><tr><td><code dir="auto">limit</code></td><td>integer 1–100</td><td>Page size, default <strong><code dir="auto">24</code></strong> — the only list on this surface that does not default to 50.</td><td><code dir="auto">24</code></td></tr><tr><td><code dir="auto">search</code></td><td>string, at least 1 character, optional</td><td>Filters by filename, server-side.</td><td><code dir="auto">logo</code></td></tr></tbody></table>
1175
+ <p>The demo’s media library is empty, so its answer is the empty envelope:</p>
1176
+ <div class="expressive-code"><figure class="frame is-terminal not-content"><figcaption class="header"><span class="title"></span><span class="sr-only">Terminal window</span></figcaption><pre data-language="sh"><code><div class="ec-line"><div class="code"><span style="--0:#82AAFF;--1:#3B61B0">curl</span><span style="--0:#D6DEEB;--1:#403F53"> </span><span style="--0:#82AAFF;--1:#3B61B0">-s</span><span style="--0:#D6DEEB;--1:#403F53"> </span><span style="--0:#82AAFF;--1:#3B61B0">-H</span><span style="--0:#D6DEEB;--1:#403F53"> </span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#ECC48D;--1:#984E4D">Authorization: Bearer vk_4ac0…9328</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#D6DEEB;--1:#403F53"> </span><span style="--0:#F78C6C;--1:#AA0982">\</span></div></div><div class="ec-line"><div class="code"><span class="indent"> </span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#ECC48D;--1:#984E4D">https://demo.velora-cms.com/api/v1/media?limit=2</span><span style="--0:#D9F5DD;--1:#111111">"</span></div></div></code></pre><div class="copy"><div aria-live="polite"></div><button title="Copy to clipboard" data-copied="Copied!" data-code="curl -s -H &#x22;Authorization: Bearer vk_4ac0…9328&#x22; \ &#x22;https://demo.velora-cms.com/api/v1/media?limit=2&#x22;"><div></div></button></div></figure></div>
1177
+ <p>That is what any list returns when nothing matches — <strong>live</strong>:</p>
1178
+ <div class="expressive-code"><figure class="frame not-content"><figcaption class="header"></figcaption><pre data-language="json"><code><div class="ec-line"><div class="code"><span style="--0:#D6DEEB;--1:#403F53">{</span><span style="--0:#7FDBCA;--1:#096E72">"items"</span><span style="--0:#D6DEEB;--1:#403F53">:[],</span><span style="--0:#7FDBCA;--1:#096E72">"nextCursor"</span><span style="--0:#D6DEEB;--1:#403F53">:</span><span style="--0:#FF6A83;--1:#A24848">null</span><span style="--0:#D6DEEB;--1:#403F53">}</span></div></div></code></pre><div class="copy"><div aria-live="polite"></div><button title="Copy to clipboard" data-copied="Copied!" data-code="{&#x22;items&#x22;:[],&#x22;nextCursor&#x22;:null}"><div></div></button></div></figure></div>
1179
+ <div class="sl-heading-wrapper level-h3"><h3 id="get-apiv1mediaid"><code dir="auto">GET /api/v1/media/:id</code></h3><a class="sl-anchor-link" href="#get-apiv1mediaid"><span aria-hidden="true" class="sl-anchor-icon"><svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor"><path d="m12.11 15.39-3.88 3.88a2.52 2.52 0 0 1-3.5 0 2.47 2.47 0 0 1 0-3.5l3.88-3.88a1 1 0 1 0-1.42-1.42l-3.88 3.89a4.48 4.48 0 0 0 6.33 6.33l3.89-3.88a1 1 0 0 0-1.42-1.42m8.58-12.08a4.49 4.49 0 0 0-6.33 0l-3.89 3.88a1 1 0 1 0 1.42 1.42l3.88-3.88a2.52 2.52 0 0 1 3.5 0 2.47 2.47 0 0 1 0 3.5l-3.88 3.88a1 1 0 0 0 0 1.42 1 1 0 0 0 1.42 0l3.88-3.89a4.49 4.49 0 0 0 0-6.33M8.83 15.17a1 1 0 0 0 .71.29 1 1 0 0 0 .71-.29l4.92-4.92a1 1 0 1 0-1.42-1.42l-4.92 4.92a1 1 0 0 0 0 1.42"></path></svg></span><span class="sr-only" data-pagefind-ignore="">Section titled “GET /api/v1/media/:id”</span></a></div>
1180
+ <p>One file’s metadata plus a fresh download URL. Unknown is a <code dir="auto">404</code> with
1181
+ <code dir="auto">{"message":"No media file found for id &#x3C;id>"}</code>.</p>
1182
+
1183
+
1184
+
1185
+
1186
+
1187
+
1188
+
1189
+
1190
+
1191
+
1192
+
1193
+
1194
+
1195
+
1196
+
1197
+
1198
+
1199
+ <table><thead><tr><th>Name</th><th>Type</th><th>Meaning</th><th>Example</th></tr></thead><tbody><tr><td><code dir="auto">:id</code></td><td>string (uuid), path parameter</td><td>The media file’s identity.</td><td><code dir="auto">9f1d3b7c-52ae-4d08-9b31-6c74a0f5e218</code></td></tr></tbody></table>
1200
+ <p>Nothing is uploaded on the demo, so this shape is schema-derived:</p>
1201
+ <div class="expressive-code"><figure class="frame not-content"><figcaption class="header"></figcaption><pre data-language="json"><code><div class="ec-line"><div class="code"><span style="--0:#D6DEEB;--1:#403F53">{</span></div></div><div class="ec-line"><div class="code"><span class="indent"> </span><span style="--0:#7FDBCA;--1:#096E72">"id"</span><span style="--0:#D6DEEB;--1:#403F53">: </span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#C789D6;--1:#7C5686">9f1d3b7c-52ae-4d08-9b31-6c74a0f5e218</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#D6DEEB;--1:#403F53">,</span></div></div><div class="ec-line"><div class="code"><span class="indent"> </span><span style="--0:#7FDBCA;--1:#096E72">"filename"</span><span style="--0:#D6DEEB;--1:#403F53">: </span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#C789D6;--1:#7C5686">cover.png</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#D6DEEB;--1:#403F53">,</span></div></div><div class="ec-line"><div class="code"><span class="indent"> </span><span style="--0:#7FDBCA;--1:#096E72">"mimeType"</span><span style="--0:#D6DEEB;--1:#403F53">: </span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#C789D6;--1:#7C5686">image/png</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#D6DEEB;--1:#403F53">,</span></div></div><div class="ec-line"><div class="code"><span class="indent"> </span><span style="--0:#7FDBCA;--1:#096E72">"size"</span><span style="--0:#D6DEEB;--1:#403F53">: </span><span style="--0:#F78C6C;--1:#AA0982">48213</span><span style="--0:#D6DEEB;--1:#403F53">,</span></div></div><div class="ec-line"><div class="code"><span class="indent"> </span><span style="--0:#7FDBCA;--1:#096E72">"downloadUrl"</span><span style="--0:#D6DEEB;--1:#403F53">: </span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#C789D6;--1:#7C5686">https://s3.example.com/velora-media/9f1d3b7c/cover.png?X-Amz-Expires=900</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#D6DEEB;--1:#403F53">,</span></div></div><div class="ec-line"><div class="code"><span class="indent"> </span><span style="--0:#7FDBCA;--1:#096E72">"uploadedAt"</span><span style="--0:#D6DEEB;--1:#403F53">: </span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#C789D6;--1:#7C5686">2026-08-31T09:12:04.006Z</span><span style="--0:#D9F5DD;--1:#111111">"</span></div></div><div class="ec-line"><div class="code"><span style="--0:#D6DEEB;--1:#403F53">}</span></div></div></code></pre><div class="copy"><div aria-live="polite"></div><button title="Copy to clipboard" data-copied="Copied!" data-code="{ &#x22;id&#x22;: &#x22;9f1d3b7c-52ae-4d08-9b31-6c74a0f5e218&#x22;, &#x22;filename&#x22;: &#x22;cover.png&#x22;, &#x22;mimeType&#x22;: &#x22;image/png&#x22;, &#x22;size&#x22;: 48213, &#x22;downloadUrl&#x22;: &#x22;https://s3.example.com/velora-media/9f1d3b7c/cover.png?X-Amz-Expires=900&#x22;, &#x22;uploadedAt&#x22;: &#x22;2026-08-31T09:12:04.006Z&#x22;}"><div></div></button></div></figure></div>
1202
+ <p>Every property:</p>
1203
+
1204
+
1205
+
1206
+
1207
+
1208
+
1209
+
1210
+
1211
+
1212
+
1213
+
1214
+
1215
+
1216
+
1217
+
1218
+
1219
+
1220
+
1221
+
1222
+
1223
+
1224
+
1225
+
1226
+
1227
+
1228
+
1229
+
1230
+
1231
+
1232
+
1233
+
1234
+
1235
+
1236
+
1237
+
1238
+
1239
+
1240
+
1241
+
1242
+
1243
+
1244
+
1245
+
1246
+
1247
+
1248
+
1249
+
1250
+ <table><thead><tr><th>Name</th><th>Type</th><th>Meaning</th><th>Example</th></tr></thead><tbody><tr><td><code dir="auto">id</code></td><td>string (uuid)</td><td>The file’s identity — this is the value an Image field stores in <code dir="auto">data</code>.</td><td><code dir="auto">9f1d3b7c-52ae-4d08-9b31-6c74a0f5e218</code></td></tr><tr><td><code dir="auto">filename</code></td><td>string, non-empty</td><td>The original filename, kept verbatim.</td><td><code dir="auto">cover.png</code></td></tr><tr><td><code dir="auto">mimeType</code></td><td>string, non-empty</td><td>Content type recorded at upload.</td><td><code dir="auto">image/png</code></td></tr><tr><td><code dir="auto">size</code></td><td>integer, 0 or more</td><td>Byte length, measured server-side.</td><td><code dir="auto">48213</code></td></tr><tr><td><code dir="auto">downloadUrl</code></td><td>string (URL)</td><td>A <strong>presigned GET</strong>, generated per response and valid for 900 seconds. Never persisted and never the same twice — refetch the record rather than caching the URL.</td><td><code dir="auto">https://s3.example.com/velora-media/…?X-Amz-Expires=900</code></td></tr><tr><td><code dir="auto">uploadedAt</code></td><td>ISO datetime string</td><td>When the record was written.</td><td><code dir="auto">2026-08-31T09:12:04.006Z</code></td></tr></tbody></table>
1251
+ <p>The uploader’s user id and the canonical object URL are on the admin shape
1252
+ only, deliberately — see <a href="/docs/v1/using-velora/media/">Media</a>. There is no
1253
+ public upload route: writes to the media library go through the admin API
1254
+ with a signed-in session.</p>
1255
+ <div class="sl-heading-wrapper level-h2"><h2 id="templates">Templates</h2><a class="sl-anchor-link" href="#templates"><span aria-hidden="true" class="sl-anchor-icon"><svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor"><path d="m12.11 15.39-3.88 3.88a2.52 2.52 0 0 1-3.5 0 2.47 2.47 0 0 1 0-3.5l3.88-3.88a1 1 0 1 0-1.42-1.42l-3.88 3.89a4.48 4.48 0 0 0 6.33 6.33l3.89-3.88a1 1 0 0 0-1.42-1.42m8.58-12.08a4.49 4.49 0 0 0-6.33 0l-3.89 3.88a1 1 0 1 0 1.42 1.42l3.88-3.88a2.52 2.52 0 0 1 3.5 0 2.47 2.47 0 0 1 0 3.5l-3.88 3.88a1 1 0 0 0 0 1.42 1 1 0 0 0 1.42 0l3.88-3.89a4.49 4.49 0 0 0 0-6.33M8.83 15.17a1 1 0 0 0 .71.29 1 1 0 0 0 .71-.29l4.92-4.92a1 1 0 1 0-1.42-1.42l-4.92 4.92a1 1 0 0 0 0 1.42"></path></svg></span><span class="sr-only" data-pagefind-ignore="">Section titled “Templates”</span></a></div>
1256
+ <div class="sl-heading-wrapper level-h3"><h3 id="get-apiv1templates"><code dir="auto">GET /api/v1/templates</code></h3><a class="sl-anchor-link" href="#get-apiv1templates"><span aria-hidden="true" class="sl-anchor-icon"><svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor"><path d="m12.11 15.39-3.88 3.88a2.52 2.52 0 0 1-3.5 0 2.47 2.47 0 0 1 0-3.5l3.88-3.88a1 1 0 1 0-1.42-1.42l-3.88 3.89a4.48 4.48 0 0 0 6.33 6.33l3.89-3.88a1 1 0 0 0-1.42-1.42m8.58-12.08a4.49 4.49 0 0 0-6.33 0l-3.89 3.88a1 1 0 1 0 1.42 1.42l3.88-3.88a2.52 2.52 0 0 1 3.5 0 2.47 2.47 0 0 1 0 3.5l-3.88 3.88a1 1 0 0 0 0 1.42 1 1 0 0 0 1.42 0l3.88-3.89a4.49 4.49 0 0 0 0-6.33M8.83 15.17a1 1 0 0 0 .71.29 1 1 0 0 0 .71-.29l4.92-4.92a1 1 0 1 0-1.42-1.42l-4.92 4.92a1 1 0 0 0 0 1.42"></path></svg></span><span class="sr-only" data-pagefind-ignore="">Section titled “GET /api/v1/templates”</span></a></div>
1257
+ <p>Templates are inert data in v1 — a name attached to a document type — so
1258
+ this route exists for consumers that render their own layouts by name.</p>
1259
+
1260
+
1261
+
1262
+
1263
+
1264
+
1265
+
1266
+
1267
+
1268
+
1269
+
1270
+
1271
+
1272
+
1273
+
1274
+
1275
+
1276
+
1277
+
1278
+
1279
+
1280
+
1281
+
1282
+
1283
+
1284
+
1285
+
1286
+
1287
+
1288
+ <table><thead><tr><th>Name</th><th>Type</th><th>Meaning</th><th>Example</th></tr></thead><tbody><tr><td><code dir="auto">cursor</code></td><td>string (uuid), optional</td><td>Pagination cursor.</td><td><code dir="auto">3f8a6c21-9d47-4b0e-8a15-7c2e9f4d6b83</code></td></tr><tr><td><code dir="auto">limit</code></td><td>integer 1–100</td><td>Page size, default <code dir="auto">50</code>.</td><td><code dir="auto">50</code></td></tr><tr><td><code dir="auto">documentTypeId</code></td><td>string (uuid), optional</td><td>Only templates for this document type.</td><td><code dir="auto">f0491ccf-0511-43a7-862b-fdf9dce55f3c</code></td></tr></tbody></table>
1289
+ <p>The demo defines none:</p>
1290
+ <div class="expressive-code"><figure class="frame is-terminal not-content"><figcaption class="header"><span class="title"></span><span class="sr-only">Terminal window</span></figcaption><pre data-language="sh"><code><div class="ec-line"><div class="code"><span style="--0:#82AAFF;--1:#3B61B0">curl</span><span style="--0:#D6DEEB;--1:#403F53"> </span><span style="--0:#82AAFF;--1:#3B61B0">-s</span><span style="--0:#D6DEEB;--1:#403F53"> </span><span style="--0:#82AAFF;--1:#3B61B0">-H</span><span style="--0:#D6DEEB;--1:#403F53"> </span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#ECC48D;--1:#984E4D">Authorization: Bearer vk_4ac0…9328</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#D6DEEB;--1:#403F53"> </span><span style="--0:#F78C6C;--1:#AA0982">\</span></div></div><div class="ec-line"><div class="code"><span class="indent"> </span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#ECC48D;--1:#984E4D">https://demo.velora-cms.com/api/v1/templates?limit=5</span><span style="--0:#D9F5DD;--1:#111111">"</span></div></div></code></pre><div class="copy"><div aria-live="polite"></div><button title="Copy to clipboard" data-copied="Copied!" data-code="curl -s -H &#x22;Authorization: Bearer vk_4ac0…9328&#x22; \ &#x22;https://demo.velora-cms.com/api/v1/templates?limit=5&#x22;"><div></div></button></div></figure></div>
1291
+ <p>Again the empty envelope — <strong>live</strong>:</p>
1292
+ <div class="expressive-code"><figure class="frame not-content"><figcaption class="header"></figcaption><pre data-language="json"><code><div class="ec-line"><div class="code"><span style="--0:#D6DEEB;--1:#403F53">{</span><span style="--0:#7FDBCA;--1:#096E72">"items"</span><span style="--0:#D6DEEB;--1:#403F53">:[],</span><span style="--0:#7FDBCA;--1:#096E72">"nextCursor"</span><span style="--0:#D6DEEB;--1:#403F53">:</span><span style="--0:#FF6A83;--1:#A24848">null</span><span style="--0:#D6DEEB;--1:#403F53">}</span></div></div></code></pre><div class="copy"><div aria-live="polite"></div><button title="Copy to clipboard" data-copied="Copied!" data-code="{&#x22;items&#x22;:[],&#x22;nextCursor&#x22;:null}"><div></div></button></div></figure></div>
1293
+ <p>A populated instance returns rows of three properties:</p>
1294
+
1295
+
1296
+
1297
+
1298
+
1299
+
1300
+
1301
+
1302
+
1303
+
1304
+
1305
+
1306
+
1307
+
1308
+
1309
+
1310
+
1311
+
1312
+
1313
+
1314
+
1315
+
1316
+
1317
+
1318
+
1319
+
1320
+
1321
+
1322
+
1323
+ <table><thead><tr><th>Name</th><th>Type</th><th>Meaning</th><th>Example</th></tr></thead><tbody><tr><td><code dir="auto">id</code></td><td>string (uuid)</td><td>The template’s identity.</td><td><code dir="auto">3f8a6c21-9d47-4b0e-8a15-7c2e9f4d6b83</code></td></tr><tr><td><code dir="auto">name</code></td><td>string, non-empty</td><td>Display name.</td><td><code dir="auto">Wide article</code></td></tr><tr><td><code dir="auto">documentTypeId</code></td><td>string (uuid)</td><td>The document type this template belongs to.</td><td><code dir="auto">f0491ccf-0511-43a7-862b-fdf9dce55f3c</code></td></tr></tbody></table>
1324
+ <div class="sl-heading-wrapper level-h2"><h2 id="graphql">GraphQL</h2><a class="sl-anchor-link" href="#graphql"><span aria-hidden="true" class="sl-anchor-icon"><svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor"><path d="m12.11 15.39-3.88 3.88a2.52 2.52 0 0 1-3.5 0 2.47 2.47 0 0 1 0-3.5l3.88-3.88a1 1 0 1 0-1.42-1.42l-3.88 3.89a4.48 4.48 0 0 0 6.33 6.33l3.89-3.88a1 1 0 0 0-1.42-1.42m8.58-12.08a4.49 4.49 0 0 0-6.33 0l-3.89 3.88a1 1 0 1 0 1.42 1.42l3.88-3.88a2.52 2.52 0 0 1 3.5 0 2.47 2.47 0 0 1 0 3.5l-3.88 3.88a1 1 0 0 0 0 1.42 1 1 0 0 0 1.42 0l3.88-3.89a4.49 4.49 0 0 0 0-6.33M8.83 15.17a1 1 0 0 0 .71.29 1 1 0 0 0 .71-.29l4.92-4.92a1 1 0 1 0-1.42-1.42l-4.92 4.92a1 1 0 0 0 0 1.42"></path></svg></span><span class="sr-only" data-pagefind-ignore="">Section titled “GraphQL”</span></a></div>
1325
+ <p><code dir="auto">/api/v1/graphql</code> is a second protocol over the identical published-item
1326
+ pipeline — not a shim over REST, and not a different data set. Reach for
1327
+ it when one view needs several related things (a page, its children, a
1328
+ referenced media file) in one round trip. It is read-only: there are no
1329
+ mutations, so writes always go through the REST routes above.</p>
1330
+ <p>The endpoint is key-gated exactly like the REST routes. <code dir="auto">POST</code> with a JSON
1331
+ body is the usual call; <code dir="auto">GET</code> with a <code dir="auto">query</code> parameter is also supported,
1332
+ and is what the live transcripts below use. <code dir="auto">POST</code> works against the demo
1333
+ too — unlike the REST write routes, the demo’s edge proxy lets
1334
+ <code dir="auto">POST /api/v1/graphql</code> through, since a query-only schema has nothing to
1335
+ write.</p>
1336
+ <div class="expressive-code"><figure class="frame is-terminal not-content"><figcaption class="header"><span class="title"></span><span class="sr-only">Terminal window</span></figcaption><pre data-language="sh"><code><div class="ec-line"><div class="code"><span style="--0:#82AAFF;--1:#3B61B0">curl</span><span style="--0:#D6DEEB;--1:#403F53"> </span><span style="--0:#82AAFF;--1:#3B61B0">-sG</span><span style="--0:#D6DEEB;--1:#403F53"> </span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#ECC48D;--1:#984E4D">https://demo.velora-cms.com/api/v1/graphql</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#D6DEEB;--1:#403F53"> </span><span style="--0:#F78C6C;--1:#AA0982">\</span></div></div><div class="ec-line"><div class="code"><span class="indent"> </span><span style="--0:#82AAFF;--1:#3B61B0">-H</span><span style="--0:#D6DEEB;--1:#403F53"> </span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#ECC48D;--1:#984E4D">Authorization: Bearer vk_4ac0…9328</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#D6DEEB;--1:#403F53"> </span><span style="--0:#F78C6C;--1:#AA0982">\</span></div></div><div class="ec-line"><div class="code"><span class="indent"> </span><span style="--0:#82AAFF;--1:#3B61B0">--data-urlencode</span><span style="--0:#D6DEEB;--1:#403F53"> </span><span style="--0:#D9F5DD;--1:#111111">'</span><span style="--0:#ECC48D;--1:#984E4D">query={ contentRoots(limit: 2) { items { id path depth } nextCursor } }</span><span style="--0:#D9F5DD;--1:#111111">'</span></div></div></code></pre><div class="copy"><div aria-live="polite"></div><button title="Copy to clipboard" data-copied="Copied!" data-code="curl -sG &#x22;https://demo.velora-cms.com/api/v1/graphql&#x22; \ -H &#x22;Authorization: Bearer vk_4ac0…9328&#x22; \ --data-urlencode &#x27;query={ contentRoots(limit: 2) { items { id path depth } nextCursor } }&#x27;"><div></div></button></div></figure></div>
1337
+ <p>The response is the standard GraphQL envelope — <strong>live</strong> — carrying the
1338
+ same rows the REST roots list returns:</p>
1339
+ <div class="expressive-code"><figure class="frame not-content"><figcaption class="header"></figcaption><pre data-language="json"><code><div class="ec-line"><div class="code"><span style="--0:#D6DEEB;--1:#403F53">{</span><span style="--0:#7FDBCA;--1:#096E72">"data"</span><span style="--0:#D6DEEB;--1:#403F53">:{</span><span style="--0:#7FDBCA;--1:#096E72">"contentRoots"</span><span style="--0:#D6DEEB;--1:#403F53">:{</span><span style="--0:#7FDBCA;--1:#096E72">"items"</span><span style="--0:#D6DEEB;--1:#403F53">:[{</span><span style="--0:#7FDBCA;--1:#096E72">"id"</span><span style="--0:#D6DEEB;--1:#403F53">:</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#C789D6;--1:#7C5686">acc3aefb-6254-465a-b088-c9578b2794af</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#D6DEEB;--1:#403F53">,</span><span style="--0:#7FDBCA;--1:#096E72">"path"</span><span style="--0:#D6DEEB;--1:#403F53">:</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#C789D6;--1:#7C5686">/jane-doe</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#D6DEEB;--1:#403F53">,</span><span style="--0:#7FDBCA;--1:#096E72">"depth"</span><span style="--0:#D6DEEB;--1:#403F53">:</span><span style="--0:#F78C6C;--1:#AA0982">0</span><span style="--0:#D6DEEB;--1:#403F53">},{</span><span style="--0:#7FDBCA;--1:#096E72">"id"</span><span style="--0:#D6DEEB;--1:#403F53">:</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#C789D6;--1:#7C5686">2e9053b1-a300-493f-a48b-58440cdf3629</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#D6DEEB;--1:#403F53">,</span><span style="--0:#7FDBCA;--1:#096E72">"path"</span><span style="--0:#D6DEEB;--1:#403F53">:</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#C789D6;--1:#7C5686">/announcements</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#D6DEEB;--1:#403F53">,</span><span style="--0:#7FDBCA;--1:#096E72">"depth"</span><span style="--0:#D6DEEB;--1:#403F53">:</span><span style="--0:#F78C6C;--1:#AA0982">0</span><span style="--0:#D6DEEB;--1:#403F53">}],</span><span style="--0:#7FDBCA;--1:#096E72">"nextCursor"</span><span style="--0:#D6DEEB;--1:#403F53">:</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#C789D6;--1:#7C5686">2e9053b1-a300-493f-a48b-58440cdf3629</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#D6DEEB;--1:#403F53">}}}</span></div></div></code></pre><div class="copy"><div aria-live="polite"></div><button title="Copy to clipboard" data-copied="Copied!" data-code="{&#x22;data&#x22;:{&#x22;contentRoots&#x22;:{&#x22;items&#x22;:[{&#x22;id&#x22;:&#x22;acc3aefb-6254-465a-b088-c9578b2794af&#x22;,&#x22;path&#x22;:&#x22;/jane-doe&#x22;,&#x22;depth&#x22;:0},{&#x22;id&#x22;:&#x22;2e9053b1-a300-493f-a48b-58440cdf3629&#x22;,&#x22;path&#x22;:&#x22;/announcements&#x22;,&#x22;depth&#x22;:0}],&#x22;nextCursor&#x22;:&#x22;2e9053b1-a300-493f-a48b-58440cdf3629&#x22;}}}"><div></div></button></div></figure></div>
1340
+ <p>Fetching one item’s fields by path is the GraphQL analogue of
1341
+ <code dir="auto">/api/v1/content/by-path</code>:</p>
1342
+ <div class="expressive-code"><figure class="frame is-terminal not-content"><figcaption class="header"><span class="title"></span><span class="sr-only">Terminal window</span></figcaption><pre data-language="sh"><code><div class="ec-line"><div class="code"><span style="--0:#82AAFF;--1:#3B61B0">curl</span><span style="--0:#D6DEEB;--1:#403F53"> </span><span style="--0:#82AAFF;--1:#3B61B0">-sG</span><span style="--0:#D6DEEB;--1:#403F53"> </span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#ECC48D;--1:#984E4D">https://demo.velora-cms.com/api/v1/graphql</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#D6DEEB;--1:#403F53"> </span><span style="--0:#F78C6C;--1:#AA0982">\</span></div></div><div class="ec-line"><div class="code"><span class="indent"> </span><span style="--0:#82AAFF;--1:#3B61B0">-H</span><span style="--0:#D6DEEB;--1:#403F53"> </span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#ECC48D;--1:#984E4D">Authorization: Bearer vk_4ac0…9328</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#D6DEEB;--1:#403F53"> </span><span style="--0:#F78C6C;--1:#AA0982">\</span></div></div><div class="ec-line"><div class="code"><span class="indent"> </span><span style="--0:#82AAFF;--1:#3B61B0">--data-urlencode</span><span style="--0:#D6DEEB;--1:#403F53"> </span><span style="--0:#D9F5DD;--1:#111111">'</span><span style="--0:#ECC48D;--1:#984E4D">query={ contentByPath(path: "/jane-doe/hello-velora") { id locale version publishedAt data } }</span><span style="--0:#D9F5DD;--1:#111111">'</span></div></div></code></pre><div class="copy"><div aria-live="polite"></div><button title="Copy to clipboard" data-copied="Copied!" data-code="curl -sG &#x22;https://demo.velora-cms.com/api/v1/graphql&#x22; \ -H &#x22;Authorization: Bearer vk_4ac0…9328&#x22; \ --data-urlencode &#x27;query={ contentByPath(path: &#x22;/jane-doe/hello-velora&#x22;) { id locale version publishedAt data } }&#x27;"><div></div></button></div></figure></div>
1343
+ <p><code dir="auto">data</code> comes back as a JSON scalar — the whole field object, exactly as
1344
+ REST serves it — <strong>live</strong>:</p>
1345
+ <div class="expressive-code"><figure class="frame not-content"><figcaption class="header"></figcaption><pre data-language="json"><code><div class="ec-line"><div class="code"><span style="--0:#D6DEEB;--1:#403F53">{</span><span style="--0:#7FDBCA;--1:#096E72">"data"</span><span style="--0:#D6DEEB;--1:#403F53">:{</span><span style="--0:#7FDBCA;--1:#096E72">"contentByPath"</span><span style="--0:#D6DEEB;--1:#403F53">:{</span><span style="--0:#7FDBCA;--1:#096E72">"id"</span><span style="--0:#D6DEEB;--1:#403F53">:</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#C789D6;--1:#7C5686">afe5b000-d81e-4c51-b60a-1f5f04d6dacf</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#D6DEEB;--1:#403F53">,</span><span style="--0:#7FDBCA;--1:#096E72">"locale"</span><span style="--0:#D6DEEB;--1:#403F53">:</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#C789D6;--1:#7C5686">en</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#D6DEEB;--1:#403F53">,</span><span style="--0:#7FDBCA;--1:#096E72">"version"</span><span style="--0:#D6DEEB;--1:#403F53">:</span><span style="--0:#F78C6C;--1:#AA0982">3</span><span style="--0:#D6DEEB;--1:#403F53">,</span><span style="--0:#7FDBCA;--1:#096E72">"publishedAt"</span><span style="--0:#D6DEEB;--1:#403F53">:</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#C789D6;--1:#7C5686">2026-08-25T15:36:10.252Z</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#D6DEEB;--1:#403F53">,</span><span style="--0:#7FDBCA;--1:#096E72">"data"</span><span style="--0:#D6DEEB;--1:#403F53">:{</span><span style="--0:#7FDBCA;--1:#096E72">"seo"</span><span style="--0:#D6DEEB;--1:#403F53">:{</span><span style="--0:#7FDBCA;--1:#096E72">"title"</span><span style="--0:#D6DEEB;--1:#403F53">:</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#C789D6;--1:#7C5686">Hello, Velora — the capstone post</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#D6DEEB;--1:#403F53">,</span><span style="--0:#7FDBCA;--1:#096E72">"description"</span><span style="--0:#D6DEEB;--1:#403F53">:</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#C789D6;--1:#7C5686">A published post proving the SEO plugin composes with the Blog starter on the deployed Velora demo.</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#D6DEEB;--1:#403F53">},</span><span style="--0:#7FDBCA;--1:#096E72">"body"</span><span style="--0:#D6DEEB;--1:#403F53">:{</span><span style="--0:#7FDBCA;--1:#096E72">"type"</span><span style="--0:#D6DEEB;--1:#403F53">:</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#C789D6;--1:#7C5686">doc</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#D6DEEB;--1:#403F53">,</span><span style="--0:#7FDBCA;--1:#096E72">"content"</span><span style="--0:#D6DEEB;--1:#403F53">:[{</span><span style="--0:#7FDBCA;--1:#096E72">"type"</span><span style="--0:#D6DEEB;--1:#403F53">:</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#C789D6;--1:#7C5686">paragraph</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#D6DEEB;--1:#403F53">,</span><span style="--0:#7FDBCA;--1:#096E72">"content"</span><span style="--0:#D6DEEB;--1:#403F53">:[{</span><span style="--0:#7FDBCA;--1:#096E72">"text"</span><span style="--0:#D6DEEB;--1:#403F53">:</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#C789D6;--1:#7C5686">This sample post was created by the Blog template. Edit or delete it freely.</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#D6DEEB;--1:#403F53">,</span><span style="--0:#7FDBCA;--1:#096E72">"type"</span><span style="--0:#D6DEEB;--1:#403F53">:</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#C789D6;--1:#7C5686">text</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#D6DEEB;--1:#403F53">}]},{</span><span style="--0:#7FDBCA;--1:#096E72">"type"</span><span style="--0:#D6DEEB;--1:#403F53">:</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#C789D6;--1:#7C5686">paragraph</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#D6DEEB;--1:#403F53">,</span><span style="--0:#7FDBCA;--1:#096E72">"content"</span><span style="--0:#D6DEEB;--1:#403F53">:[{</span><span style="--0:#7FDBCA;--1:#096E72">"text"</span><span style="--0:#D6DEEB;--1:#403F53">:</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#C789D6;--1:#7C5686">Updated live during the Session 168 capstone story.</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#D6DEEB;--1:#403F53">,</span><span style="--0:#7FDBCA;--1:#096E72">"type"</span><span style="--0:#D6DEEB;--1:#403F53">:</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#C789D6;--1:#7C5686">text</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#D6DEEB;--1:#403F53">}]}]},</span><span style="--0:#7FDBCA;--1:#096E72">"tags"</span><span style="--0:#D6DEEB;--1:#403F53">:</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#C789D6;--1:#7C5686">announcements</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#D6DEEB;--1:#403F53">,</span><span style="--0:#7FDBCA;--1:#096E72">"title"</span><span style="--0:#D6DEEB;--1:#403F53">:</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#C789D6;--1:#7C5686">Hello, Velora</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#D6DEEB;--1:#403F53">,</span><span style="--0:#7FDBCA;--1:#096E72">"publishedDate"</span><span style="--0:#D6DEEB;--1:#403F53">:</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#C789D6;--1:#7C5686">2026-08-13</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#D6DEEB;--1:#403F53">}}}}</span></div></div></code></pre><div class="copy"><div aria-live="polite"></div><button title="Copy to clipboard" data-copied="Copied!" data-code="{&#x22;data&#x22;:{&#x22;contentByPath&#x22;:{&#x22;id&#x22;:&#x22;afe5b000-d81e-4c51-b60a-1f5f04d6dacf&#x22;,&#x22;locale&#x22;:&#x22;en&#x22;,&#x22;version&#x22;:3,&#x22;publishedAt&#x22;:&#x22;2026-08-25T15:36:10.252Z&#x22;,&#x22;data&#x22;:{&#x22;seo&#x22;:{&#x22;title&#x22;:&#x22;Hello, Velora — the capstone post&#x22;,&#x22;description&#x22;:&#x22;A published post proving the SEO plugin composes with the Blog starter on the deployed Velora demo.&#x22;},&#x22;body&#x22;:{&#x22;type&#x22;:&#x22;doc&#x22;,&#x22;content&#x22;:[{&#x22;type&#x22;:&#x22;paragraph&#x22;,&#x22;content&#x22;:[{&#x22;text&#x22;:&#x22;This sample post was created by the Blog template. Edit or delete it freely.&#x22;,&#x22;type&#x22;:&#x22;text&#x22;}]},{&#x22;type&#x22;:&#x22;paragraph&#x22;,&#x22;content&#x22;:[{&#x22;text&#x22;:&#x22;Updated live during the Session 168 capstone story.&#x22;,&#x22;type&#x22;:&#x22;text&#x22;}]}]},&#x22;tags&#x22;:&#x22;announcements&#x22;,&#x22;title&#x22;:&#x22;Hello, Velora&#x22;,&#x22;publishedDate&#x22;:&#x22;2026-08-13&#x22;}}}}"><div></div></button></div></figure></div>
1346
+ <p>Every query on the schema, with its arguments:</p>
1347
+
1348
+
1349
+
1350
+
1351
+
1352
+
1353
+
1354
+
1355
+
1356
+
1357
+
1358
+
1359
+
1360
+
1361
+
1362
+
1363
+
1364
+
1365
+
1366
+
1367
+
1368
+
1369
+
1370
+
1371
+
1372
+
1373
+
1374
+
1375
+
1376
+
1377
+
1378
+
1379
+
1380
+
1381
+
1382
+
1383
+
1384
+
1385
+
1386
+
1387
+
1388
+
1389
+
1390
+
1391
+
1392
+
1393
+
1394
+
1395
+
1396
+
1397
+
1398
+
1399
+
1400
+
1401
+
1402
+
1403
+
1404
+
1405
+
1406
+
1407
+
1408
+
1409
+
1410
+
1411
+
1412
+
1413
+
1414
+
1415
+
1416
+
1417
+
1418
+ <table><thead><tr><th>Name</th><th>Type</th><th>Meaning</th><th>Example</th></tr></thead><tbody><tr><td><code dir="auto">content</code></td><td><code dir="auto">PublicContentItem</code>, nullable</td><td>One item by id. Arguments: <code dir="auto">id!</code>, <code dir="auto">locale</code>, <code dir="auto">fallback</code>, <code dir="auto">previewToken</code>. Null for unknown, draft and unresolvable-translation alike.</td><td><code dir="auto">content(id: "acc3aefb-…") { data }</code></td></tr><tr><td><code dir="auto">contentByPath</code></td><td><code dir="auto">PublicContentItem</code>, nullable</td><td>One item by tree path. Arguments: <code dir="auto">path!</code>, <code dir="auto">locale</code>, <code dir="auto">fallback</code>. No preview token on this query.</td><td><code dir="auto">contentByPath(path: "/announcements") { id }</code></td></tr><tr><td><code dir="auto">contentRoots</code></td><td><code dir="auto">ContentNodeConnection</code></td><td>Published root nodes. Arguments: <code dir="auto">cursor</code>, <code dir="auto">limit</code>. Never null.</td><td><code dir="auto">contentRoots(limit: 2) { items { path } }</code></td></tr><tr><td><code dir="auto">contentChildren</code></td><td><code dir="auto">ContentNodeConnection</code>, nullable</td><td>Direct children of a published node. Arguments: <code dir="auto">id!</code>, <code dir="auto">cursor</code>, <code dir="auto">limit</code>. Null when the parent is unknown or unpublished.</td><td><code dir="auto">contentChildren(id: "acc3aefb-…") { items { path } }</code></td></tr><tr><td><code dir="auto">contents</code></td><td><code dir="auto">ContentNodeConnection</code>, nullable</td><td>Filtered search. Arguments: <code dir="auto">documentTypeId</code>, <code dir="auto">under</code>, <code dir="auto">q</code>, <code dir="auto">cursor</code>, <code dir="auto">limit</code>. Null when <code dir="auto">under</code> names no published node.</td><td><code dir="auto">contents(q: "hello") { items { id } }</code></td></tr><tr><td><code dir="auto">documentType</code></td><td><code dir="auto">PublicDocumentType</code>, nullable</td><td>One document type by id. Argument: <code dir="auto">id!</code>.</td><td><code dir="auto">documentType(id: "f0491ccf-…") { name }</code></td></tr><tr><td><code dir="auto">documentTypes</code></td><td><code dir="auto">DocumentTypeConnection</code></td><td>All document types. Arguments: <code dir="auto">cursor</code>, <code dir="auto">limit</code>. Never null.</td><td><code dir="auto">documentTypes { items { name } }</code></td></tr><tr><td><code dir="auto">templates</code></td><td><code dir="auto">TemplateConnection</code></td><td>Templates. Arguments: <code dir="auto">cursor</code>, <code dir="auto">limit</code>, <code dir="auto">documentTypeId</code>. Never null.</td><td><code dir="auto">templates { items { name } }</code></td></tr><tr><td><code dir="auto">mediaItem</code></td><td><code dir="auto">PublicMediaFile</code>, nullable</td><td>One media file by id. Argument: <code dir="auto">id!</code>.</td><td><code dir="auto">mediaItem(id: "9f1d3b7c-…") { downloadUrl }</code></td></tr><tr><td><code dir="auto">media</code></td><td><code dir="auto">MediaConnection</code></td><td>The media library. Arguments: <code dir="auto">cursor</code>, <code dir="auto">limit</code>, <code dir="auto">search</code>. Never null.</td><td><code dir="auto">media(limit: 5) { items { filename } }</code></td></tr></tbody></table>
1419
+ <p>Every connection type has the same two fields, <code dir="auto">items</code> and <code dir="auto">nextCursor</code>,
1420
+ and the object types carry exactly the properties the REST tables above
1421
+ describe. Argument validation is shared with REST: a <code dir="auto">limit</code> outside 1–100
1422
+ or a <code dir="auto">q</code> under two characters is an entry in the response’s <code dir="auto">errors</code>
1423
+ array, never a silent clamp.</p>
1424
+ <p>Two guards apply to every operation:</p>
1425
+
1426
+
1427
+
1428
+
1429
+
1430
+
1431
+
1432
+
1433
+
1434
+
1435
+
1436
+
1437
+
1438
+
1439
+
1440
+
1441
+
1442
+
1443
+
1444
+
1445
+
1446
+
1447
+
1448
+ <table><thead><tr><th>Name</th><th>Type</th><th>Meaning</th><th>Example</th></tr></thead><tbody><tr><td>Depth limit</td><td>integer</td><td>Rejects a query nested deeper than <code dir="auto">VELORA_GRAPHQL_MAX_DEPTH</code>. Default <code dir="auto">8</code>.</td><td><code dir="auto">8</code></td></tr><tr><td>Complexity limit</td><td>integer</td><td>Rejects an operation selecting more than <code dir="auto">VELORA_GRAPHQL_MAX_COMPLEXITY</code> fields. Every field selection counts 1; aliased duplicates each count; a fragment’s fields count once <strong>per spread site</strong>, so spreading one shared fragment into ten aliased roots costs ten times its fields. Default <code dir="auto">500</code>.</td><td><code dir="auto">500</code></td></tr></tbody></table>
1449
+ <p>Introspection is disabled unless <code dir="auto">VELORA_PUBLIC_DOCS</code> is on, so a hardened
1450
+ deployment answers queries but will not describe its own schema.</p>
1451
+ <div class="sl-heading-wrapper level-h3"><h3 id="graphiql">GraphiQL</h3><a class="sl-anchor-link" href="#graphiql"><span aria-hidden="true" class="sl-anchor-icon"><svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor"><path d="m12.11 15.39-3.88 3.88a2.52 2.52 0 0 1-3.5 0 2.47 2.47 0 0 1 0-3.5l3.88-3.88a1 1 0 1 0-1.42-1.42l-3.88 3.89a4.48 4.48 0 0 0 6.33 6.33l3.89-3.88a1 1 0 0 0-1.42-1.42m8.58-12.08a4.49 4.49 0 0 0-6.33 0l-3.89 3.88a1 1 0 1 0 1.42 1.42l3.88-3.88a2.52 2.52 0 0 1 3.5 0 2.47 2.47 0 0 1 0 3.5l-3.88 3.88a1 1 0 0 0 0 1.42 1 1 0 0 0 1.42 0l3.88-3.89a4.49 4.49 0 0 0 0-6.33M8.83 15.17a1 1 0 0 0 .71.29 1 1 0 0 0 .71-.29l4.92-4.92a1 1 0 1 0-1.42-1.42l-4.92 4.92a1 1 0 0 0 0 1.42"></path></svg></span><span class="sr-only" data-pagefind-ignore="">Section titled “GraphiQL”</span></a></div>
1452
+ <p>When <code dir="auto">VELORA_PUBLIC_DOCS</code> is on, an in-browser explorer is served at
1453
+ <code dir="auto">/api/v1/graphiql</code>. The page itself is not key-gated — it is HTML and
1454
+ JavaScript, like a Swagger UI — but every query it sends is, so opening it
1455
+ without a key shows the schema, not the data.</p>
1456
+ <p>The demo has <code dir="auto">VELORA_PUBLIC_DOCS</code> on, and its edge proxy allows
1457
+ <code dir="auto">POST /api/v1/graphql</code>, so GraphiQL is fully usable there — <strong>live</strong> at
1458
+ <a href="https://demo.velora-cms.com/api/v1/graphiql">demo.velora-cms.com/api/v1/graphiql</a>,
1459
+ key and all.</p>
1460
+ <div class="sl-heading-wrapper level-h2"><h2 id="openapi">OpenAPI</h2><a class="sl-anchor-link" href="#openapi"><span aria-hidden="true" class="sl-anchor-icon"><svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor"><path d="m12.11 15.39-3.88 3.88a2.52 2.52 0 0 1-3.5 0 2.47 2.47 0 0 1 0-3.5l3.88-3.88a1 1 0 1 0-1.42-1.42l-3.88 3.89a4.48 4.48 0 0 0 6.33 6.33l3.89-3.88a1 1 0 0 0-1.42-1.42m8.58-12.08a4.49 4.49 0 0 0-6.33 0l-3.89 3.88a1 1 0 1 0 1.42 1.42l3.88-3.88a2.52 2.52 0 0 1 3.5 0 2.47 2.47 0 0 1 0 3.5l-3.88 3.88a1 1 0 0 0 0 1.42 1 1 0 0 0 1.42 0l3.88-3.89a4.49 4.49 0 0 0 0-6.33M8.83 15.17a1 1 0 0 0 .71.29 1 1 0 0 0 .71-.29l4.92-4.92a1 1 0 1 0-1.42-1.42l-4.92 4.92a1 1 0 0 0 0 1.42"></path></svg></span><span class="sr-only" data-pagefind-ignore="">Section titled “OpenAPI”</span></a></div>
1461
+ <p><code dir="auto">/api/v1/docs</code> is a Swagger UI generated from the same Zod schemas the
1462
+ routes validate against, filtered to the public surface only — the admin
1463
+ API never appears in it. It is served when <code dir="auto">VELORA_HEADLESS</code> <strong>and</strong>
1464
+ <code dir="auto">VELORA_PUBLIC_DOCS</code> are both on; turning the latter off <code dir="auto">404</code>s the docs
1465
+ while the API keeps answering, which is the usual post-launch hardening
1466
+ step.</p>
1467
+ <div class="expressive-code"><figure class="frame is-terminal not-content"><figcaption class="header"><span class="title"></span><span class="sr-only">Terminal window</span></figcaption><pre data-language="sh"><code><div class="ec-line"><div class="code"><span style="--0:#82AAFF;--1:#3B61B0">curl</span><span style="--0:#D6DEEB;--1:#403F53"> </span><span style="--0:#82AAFF;--1:#3B61B0">-si</span><span style="--0:#D6DEEB;--1:#403F53"> </span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#ECC48D;--1:#984E4D">https://demo.velora-cms.com/api/v1/docs</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#D6DEEB;--1:#403F53"> </span><span style="--0:#7FDBCA;--1:#096E72">|</span><span style="--0:#D6DEEB;--1:#403F53"> </span><span style="--0:#82AAFF;--1:#3B61B0">head</span><span style="--0:#D6DEEB;--1:#403F53"> </span><span style="--0:#82AAFF;--1:#3B61B0">-1</span></div></div></code></pre><div class="copy"><div aria-live="polite"></div><button title="Copy to clipboard" data-copied="Copied!" data-code="curl -si &#x22;https://demo.velora-cms.com/api/v1/docs&#x22; | head -1"><div></div></button></div></figure></div>
1468
+ <p>The demo serves it — <strong>live</strong> — so the generated reference is browsable
1469
+ alongside this page:</p>
1470
+ <div class="expressive-code"><figure class="frame not-content"><figcaption class="header"></figcaption><pre data-language="plaintext"><code><div class="ec-line"><div class="code"><span style="--0:#d6deeb;--1:#403f53">HTTP/1.1 200 OK</span></div></div></code></pre><div class="copy"><div aria-live="polite"></div><button title="Copy to clipboard" data-copied="Copied!" data-code="HTTP/1.1 200 OK"><div></div></button></div></figure></div>
1471
+ <p>GraphQL is deliberately absent from that spec: an arbitrary
1472
+ query/variables body is not expressible in OpenAPI, which is why the
1473
+ GraphQL surface gets GraphiQL instead.</p>
1474
+ <div class="sl-heading-wrapper level-h2"><h2 id="errors">Errors</h2><a class="sl-anchor-link" href="#errors"><span aria-hidden="true" class="sl-anchor-icon"><svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor"><path d="m12.11 15.39-3.88 3.88a2.52 2.52 0 0 1-3.5 0 2.47 2.47 0 0 1 0-3.5l3.88-3.88a1 1 0 1 0-1.42-1.42l-3.88 3.89a4.48 4.48 0 0 0 6.33 6.33l3.89-3.88a1 1 0 0 0-1.42-1.42m8.58-12.08a4.49 4.49 0 0 0-6.33 0l-3.89 3.88a1 1 0 1 0 1.42 1.42l3.88-3.88a2.52 2.52 0 0 1 3.5 0 2.47 2.47 0 0 1 0 3.5l-3.88 3.88a1 1 0 0 0 0 1.42 1 1 0 0 0 1.42 0l3.88-3.89a4.49 4.49 0 0 0 0-6.33M8.83 15.17a1 1 0 0 0 .71.29 1 1 0 0 0 .71-.29l4.92-4.92a1 1 0 1 0-1.42-1.42l-4.92 4.92a1 1 0 0 0 0 1.42"></path></svg></span><span class="sr-only" data-pagefind-ignore="">Section titled “Errors”</span></a></div>
1475
+ <p>Every error body on this surface is JSON with a <code dir="auto">message</code>. Validation
1476
+ failures add <code dir="auto">fieldErrors</code>; rate-limit rejections add <code dir="auto">statusCode</code>. <code dir="auto">401</code>
1477
+ and <code dir="auto">403</code> are distinct answers: <code dir="auto">401</code> means the key was not accepted at
1478
+ all, <code dir="auto">403</code> means it was accepted and is not allowed to write.</p>
1479
+
1480
+
1481
+
1482
+
1483
+
1484
+
1485
+
1486
+
1487
+
1488
+
1489
+
1490
+
1491
+
1492
+
1493
+
1494
+
1495
+
1496
+
1497
+
1498
+
1499
+
1500
+
1501
+
1502
+
1503
+
1504
+
1505
+
1506
+
1507
+
1508
+
1509
+
1510
+
1511
+
1512
+
1513
+
1514
+
1515
+
1516
+
1517
+
1518
+
1519
+
1520
+
1521
+
1522
+
1523
+
1524
+
1525
+
1526
+ <table><thead><tr><th>Name</th><th>Type</th><th>Meaning</th><th>Example</th></tr></thead><tbody><tr><td><code dir="auto">400</code></td><td>Bad Request</td><td>A parameter failed schema validation, an unknown <code dir="auto">format</code> was asked for, or a write failed field validation (then <code dir="auto">fieldErrors</code> is present).</td><td><code dir="auto">{"message":"Unknown format nope:thing"}</code></td></tr><tr><td><code dir="auto">401</code></td><td>Unauthorized</td><td>No key, a malformed key, an unknown key, or a revoked key. Always the same body.</td><td><code dir="auto">{"message":"A valid API key is required"}</code></td></tr><tr><td><code dir="auto">403</code></td><td>Forbidden</td><td>A valid, unrevoked <code dir="auto">read</code> key on one of the four write routes. Reads never answer <code dir="auto">403</code>.</td><td><code dir="auto">{"message":"This API key is read-only"}</code></td></tr><tr><td><code dir="auto">404</code></td><td>Not Found</td><td>On reads, the uniform published-only miss. <code dir="auto">POST /api/v1/content</code> and <code dir="auto">PUT</code>/<code dir="auto">PATCH /api/v1/content/:id</code> name the id or parent id that was not found; <code dir="auto">POST /api/v1/content/:id/publish</code> answers the same uniform read message instead.</td><td><code dir="auto">{"message":"No published content found"}</code></td></tr><tr><td><code dir="auto">429</code></td><td>Too Many Requests</td><td>The rate limit for your lane. Carries a <code dir="auto">Retry-After</code> header.</td><td><code dir="auto">{"statusCode":429,"message":"Rate limit exceeded, retry in 42 seconds"}</code></td></tr><tr><td><code dir="auto">500</code></td><td>Internal Server Error</td><td>Rare and specific: a node whose current-version pointer has no matching version row.</td><td><code dir="auto">{"message":"current_version (3) for … has no matching content version"}</code></td></tr></tbody></table>
1527
+ <p>A querystring that fails validation answers Fastify’s validation body,
1528
+ which names the offending parameter:</p>
1529
+ <div class="expressive-code"><figure class="frame is-terminal not-content"><figcaption class="header"><span class="title"></span><span class="sr-only">Terminal window</span></figcaption><pre data-language="sh"><code><div class="ec-line"><div class="code"><span style="--0:#82AAFF;--1:#3B61B0">curl</span><span style="--0:#D6DEEB;--1:#403F53"> </span><span style="--0:#82AAFF;--1:#3B61B0">-s</span><span style="--0:#D6DEEB;--1:#403F53"> </span><span style="--0:#82AAFF;--1:#3B61B0">-H</span><span style="--0:#D6DEEB;--1:#403F53"> </span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#ECC48D;--1:#984E4D">Authorization: Bearer vk_4ac0…9328</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#D6DEEB;--1:#403F53"> </span><span style="--0:#F78C6C;--1:#AA0982">\</span></div></div><div class="ec-line"><div class="code"><span class="indent"> </span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#ECC48D;--1:#984E4D">https://demo.velora-cms.com/api/v1/content?limit=500</span><span style="--0:#D9F5DD;--1:#111111">"</span></div></div></code></pre><div class="copy"><div aria-live="polite"></div><button title="Copy to clipboard" data-copied="Copied!" data-code="curl -s -H &#x22;Authorization: Bearer vk_4ac0…9328&#x22; \ &#x22;https://demo.velora-cms.com/api/v1/content?limit=500&#x22;"><div></div></button></div></figure></div>
1530
+ <p>The message names both the field and the rule — <strong>live</strong>:</p>
1531
+ <div class="expressive-code"><figure class="frame not-content"><figcaption class="header"></figcaption><pre data-language="json"><code><div class="ec-line"><div class="code"><span style="--0:#D6DEEB;--1:#403F53">{</span><span style="--0:#7FDBCA;--1:#096E72">"statusCode"</span><span style="--0:#D6DEEB;--1:#403F53">:</span><span style="--0:#F78C6C;--1:#AA0982">400</span><span style="--0:#D6DEEB;--1:#403F53">,</span><span style="--0:#7FDBCA;--1:#096E72">"code"</span><span style="--0:#D6DEEB;--1:#403F53">:</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#C789D6;--1:#7C5686">FST_ERR_VALIDATION</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#D6DEEB;--1:#403F53">,</span><span style="--0:#7FDBCA;--1:#096E72">"error"</span><span style="--0:#D6DEEB;--1:#403F53">:</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#C789D6;--1:#7C5686">Bad Request</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#D6DEEB;--1:#403F53">,</span><span style="--0:#7FDBCA;--1:#096E72">"message"</span><span style="--0:#D6DEEB;--1:#403F53">:</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#C789D6;--1:#7C5686">querystring/limit Too big: expected number to be &#x3C;=100</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#D6DEEB;--1:#403F53">}</span></div></div></code></pre><div class="copy"><div aria-live="polite"></div><button title="Copy to clipboard" data-copied="Copied!" data-code="{&#x22;statusCode&#x22;:400,&#x22;code&#x22;:&#x22;FST_ERR_VALIDATION&#x22;,&#x22;error&#x22;:&#x22;Bad Request&#x22;,&#x22;message&#x22;:&#x22;querystring/limit Too big: expected number to be <=100&#x22;}"><div></div></button></div></figure></div>
1532
+ <div class="sl-heading-wrapper level-h2"><h2 id="rate-limits">Rate limits</h2><a class="sl-anchor-link" href="#rate-limits"><span aria-hidden="true" class="sl-anchor-icon"><svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor"><path d="m12.11 15.39-3.88 3.88a2.52 2.52 0 0 1-3.5 0 2.47 2.47 0 0 1 0-3.5l3.88-3.88a1 1 0 1 0-1.42-1.42l-3.88 3.89a4.48 4.48 0 0 0 6.33 6.33l3.89-3.88a1 1 0 0 0-1.42-1.42m8.58-12.08a4.49 4.49 0 0 0-6.33 0l-3.89 3.88a1 1 0 1 0 1.42 1.42l3.88-3.88a2.52 2.52 0 0 1 3.5 0 2.47 2.47 0 0 1 0 3.5l-3.88 3.88a1 1 0 0 0 0 1.42 1 1 0 0 0 1.42 0l3.88-3.89a4.49 4.49 0 0 0 0-6.33M8.83 15.17a1 1 0 0 0 .71.29 1 1 0 0 0 .71-.29l4.92-4.92a1 1 0 1 0-1.42-1.42l-4.92 4.92a1 1 0 0 0 0 1.42"></path></svg></span><span class="sr-only" data-pagefind-ignore="">Section titled “Rate limits”</span></a></div>
1533
+ <p>Requests are bucketed by what they actually present, and each bucket has
1534
+ its own ceiling.</p>
1535
+
1536
+
1537
+
1538
+
1539
+
1540
+
1541
+
1542
+
1543
+
1544
+
1545
+
1546
+
1547
+
1548
+
1549
+
1550
+
1551
+
1552
+
1553
+
1554
+
1555
+
1556
+
1557
+
1558
+
1559
+
1560
+
1561
+
1562
+
1563
+
1564
+ <table><thead><tr><th>Name</th><th>Type</th><th>Meaning</th><th>Example</th></tr></thead><tbody><tr><td>Key lane</td><td>integer per window</td><td>A <code dir="auto">vk_</code> token that resolves to a real, unrevoked key gets its own bucket, <code dir="auto">VELORA_RATE_LIMIT_MAX</code> requests per window. Default <code dir="auto">100</code>.</td><td><code dir="auto">100</code></td></tr><tr><td>Anonymous lane</td><td>integer per window</td><td>No token, or a <code dir="auto">vk_</code>-shaped token that does not resolve, falls back to a per-IP bucket capped at <code dir="auto">VELORA_RATE_LIMIT_IP_MAX</code>. Default <code dir="auto">60</code>. This is why minting random <code dir="auto">vk_</code> strings cannot buy a bigger allowance.</td><td><code dir="auto">60</code></td></tr><tr><td>Window</td><td>duration string</td><td><code dir="auto">VELORA_RATE_LIMIT_WINDOW</code>, default <code dir="auto">1 minute</code>.</td><td><code dir="auto">1 minute</code></td></tr></tbody></table>
1565
+ <p>Every response carries the current state of your bucket:</p>
1566
+ <div class="expressive-code"><figure class="frame is-terminal not-content"><figcaption class="header"><span class="title"></span><span class="sr-only">Terminal window</span></figcaption><pre data-language="sh"><code><div class="ec-line"><div class="code"><span style="--0:#82AAFF;--1:#3B61B0">curl</span><span style="--0:#D6DEEB;--1:#403F53"> </span><span style="--0:#82AAFF;--1:#3B61B0">-si</span><span style="--0:#D6DEEB;--1:#403F53"> </span><span style="--0:#82AAFF;--1:#3B61B0">-H</span><span style="--0:#D6DEEB;--1:#403F53"> </span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#ECC48D;--1:#984E4D">Authorization: Bearer vk_4ac0…9328</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#D6DEEB;--1:#403F53"> </span><span style="--0:#F78C6C;--1:#AA0982">\</span></div></div><div class="ec-line"><div class="code"><span class="indent"> </span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#ECC48D;--1:#984E4D">https://demo.velora-cms.com/api/v1/content?limit=1</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#D6DEEB;--1:#403F53"> </span><span style="--0:#7FDBCA;--1:#096E72">|</span><span style="--0:#D6DEEB;--1:#403F53"> </span><span style="--0:#82AAFF;--1:#3B61B0">grep</span><span style="--0:#D6DEEB;--1:#403F53"> </span><span style="--0:#82AAFF;--1:#3B61B0">-i</span><span style="--0:#D6DEEB;--1:#403F53"> </span><span style="--0:#ECC48D;--1:#3B61B0">ratelimit</span></div></div></code></pre><div class="copy"><div aria-live="polite"></div><button title="Copy to clipboard" data-copied="Copied!" data-code="curl -si -H &#x22;Authorization: Bearer vk_4ac0…9328&#x22; \ &#x22;https://demo.velora-cms.com/api/v1/content?limit=1&#x22; | grep -i ratelimit"><div></div></button></div></figure></div>
1567
+ <p>Three headers come back — <strong>live</strong>, from a demo call partway through its
1568
+ window:</p>
1569
+ <div class="expressive-code"><figure class="frame not-content"><figcaption class="header"></figcaption><pre data-language="plaintext"><code><div class="ec-line"><div class="code"><span style="--0:#d6deeb;--1:#403f53">x-ratelimit-limit: 100</span></div></div><div class="ec-line"><div class="code"><span style="--0:#d6deeb;--1:#403f53">x-ratelimit-remaining: 93</span></div></div><div class="ec-line"><div class="code"><span style="--0:#d6deeb;--1:#403f53">x-ratelimit-reset: 19</span></div></div></code></pre><div class="copy"><div aria-live="polite"></div><button title="Copy to clipboard" data-copied="Copied!" data-code="x-ratelimit-limit: 100x-ratelimit-remaining: 93x-ratelimit-reset: 19"><div></div></button></div></figure></div>
1570
+
1571
+
1572
+
1573
+
1574
+
1575
+
1576
+
1577
+
1578
+
1579
+
1580
+
1581
+
1582
+
1583
+
1584
+
1585
+
1586
+
1587
+
1588
+
1589
+
1590
+
1591
+
1592
+
1593
+
1594
+
1595
+
1596
+
1597
+
1598
+
1599
+
1600
+
1601
+
1602
+
1603
+
1604
+
1605
+ <table><thead><tr><th>Name</th><th>Type</th><th>Meaning</th><th>Example</th></tr></thead><tbody><tr><td><code dir="auto">x-ratelimit-limit</code></td><td>integer</td><td>The ceiling for your lane.</td><td><code dir="auto">100</code></td></tr><tr><td><code dir="auto">x-ratelimit-remaining</code></td><td>integer</td><td>Requests left in this window.</td><td><code dir="auto">93</code></td></tr><tr><td><code dir="auto">x-ratelimit-reset</code></td><td>integer (seconds)</td><td>Seconds until the window rolls over.</td><td><code dir="auto">19</code></td></tr><tr><td><code dir="auto">retry-after</code></td><td>integer (seconds)</td><td>Sent on a <code dir="auto">429</code> only.</td><td><code dir="auto">19</code></td></tr></tbody></table>
1606
+ <p>Revoking a key stops it immediately — every request is re-checked against
1607
+ the database — even though the rate limiter may keep that key’s bucket for
1608
+ up to 30 seconds afterwards. The bucket grants no access; it is only
1609
+ bookkeeping.</p>
1610
+ <div class="sl-heading-wrapper level-h2"><h2 id="next">Next</h2><a class="sl-anchor-link" href="#next"><span aria-hidden="true" class="sl-anchor-icon"><svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor"><path d="m12.11 15.39-3.88 3.88a2.52 2.52 0 0 1-3.5 0 2.47 2.47 0 0 1 0-3.5l3.88-3.88a1 1 0 1 0-1.42-1.42l-3.88 3.89a4.48 4.48 0 0 0 6.33 6.33l3.89-3.88a1 1 0 0 0-1.42-1.42m8.58-12.08a4.49 4.49 0 0 0-6.33 0l-3.89 3.88a1 1 0 1 0 1.42 1.42l3.88-3.88a2.52 2.52 0 0 1 3.5 0 2.47 2.47 0 0 1 0 3.5l-3.88 3.88a1 1 0 0 0 0 1.42 1 1 0 0 0 1.42 0l3.88-3.89a4.49 4.49 0 0 0 0-6.33M8.83 15.17a1 1 0 0 0 .71.29 1 1 0 0 0 .71-.29l4.92-4.92a1 1 0 1 0-1.42-1.42l-4.92 4.92a1 1 0 0 0 0 1.42"></path></svg></span><span class="sr-only" data-pagefind-ignore="">Section titled “Next”</span></a></div>
1611
+ <ul>
1612
+ <li><a href="/docs/v1/using-velora/build-a-headless-frontend/">Build a headless frontend</a> —
1613
+ a working site on these endpoints, one file, no dependencies.</li>
1614
+ <li><a href="/docs/v1/using-velora/users-and-roles/">Users and roles</a> — creating, listing
1615
+ and revoking API keys.</li>
1616
+ <li><a href="/docs/v1/using-velora/content-model/">Content model</a> — where document types,
1617
+ fields and tree rules come from.</li>
1618
+ <li><a href="/docs/v1/using-velora/editing/">Editing</a> — what a version is, and why
1619
+ publishing never deletes anything.</li>
1620
+ </ul></div><footer class="sl-flex astro-gjkl2a5w"><div class="meta sl-flex astro-gjkl2a5w"></div><div class="pagination-links print:hidden astro-3tj6ibx2" dir="ltr"><a href="/docs/v1/using-velora/installing-plugins/" rel="prev" class="astro-3tj6ibx2"><svg aria-hidden="true" class="astro-3tj6ibx2 astro-24n5ceuf" width="16" height="16" viewBox="0 0 24 24" fill="currentColor" style="--sl-icon-size: 1.5rem;"><path d="M17 11H9.41l3.3-3.29a1.004 1.004 0 1 0-1.42-1.42l-5 5a1 1 0 0 0-.21.33 1 1 0 0 0 0 .76 1 1 0 0 0 .21.33l5 5a1.002 1.002 0 0 0 1.639-.325 1 1 0 0 0-.219-1.095L9.41 13H17a1 1 0 0 0 0-2Z"/></svg><span class="astro-3tj6ibx2">Previous<br class="astro-3tj6ibx2"><span class="link-title astro-3tj6ibx2">Installing plugins</span></span></a><a href="/docs/v1/using-velora/build-a-headless-frontend/" rel="next" class="astro-3tj6ibx2"><svg aria-hidden="true" class="astro-3tj6ibx2 astro-24n5ceuf" width="16" height="16" viewBox="0 0 24 24" fill="currentColor" style="--sl-icon-size: 1.5rem;"><path d="M17.92 11.62a1.001 1.001 0 0 0-.21-.33l-5-5a1.003 1.003 0 1 0-1.42 1.42l3.3 3.29H7a1 1 0 0 0 0 2h7.59l-3.3 3.29a1.002 1.002 0 0 0 .325 1.639 1 1 0 0 0 1.095-.219l5-5a1 1 0 0 0 .21-.33 1 1 0 0 0 0-.76Z"/></svg><span class="astro-3tj6ibx2">Next<br class="astro-3tj6ibx2"><span class="link-title astro-3tj6ibx2">Build a headless frontend</span></span></a></div></footer><div class="velora-bundle-stamp astro-llmj63sw">Velora 0.9.0 &middot; bundled documentation</div></div></div></main></div></div></div></div></body></html>