domma-cms 0.93.0 → 0.94.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 (64) hide show
  1. package/admin/css/admin.css +1 -1
  2. package/admin/js/app.js +2 -2
  3. package/admin/js/templates/docs/api-actions.html +86 -64
  4. package/admin/js/templates/docs/api-authentication.html +159 -123
  5. package/admin/js/templates/docs/api-builder.html +197 -0
  6. package/admin/js/templates/docs/api-collections.html +199 -259
  7. package/admin/js/templates/docs/api-external.html +225 -0
  8. package/admin/js/templates/docs/api-forms.html +268 -0
  9. package/admin/js/templates/docs/api-layouts.html +70 -45
  10. package/admin/js/templates/docs/api-media.html +57 -80
  11. package/admin/js/templates/docs/api-navigation.html +66 -22
  12. package/admin/js/templates/docs/api-pages.html +109 -129
  13. package/admin/js/templates/docs/api-plugins.html +123 -61
  14. package/admin/js/templates/docs/api-scaffold.html +185 -0
  15. package/admin/js/templates/docs/api-settings.html +72 -64
  16. package/admin/js/templates/docs/api-users.html +74 -107
  17. package/admin/js/templates/docs/api-views.html +68 -54
  18. package/admin/js/templates/docs/components-howto.html +20 -17
  19. package/admin/js/templates/docs/components-reference.html +13 -16
  20. package/admin/js/templates/docs/components-rules.html +7 -6
  21. package/admin/js/templates/docs/components-walkthrough.html +19 -19
  22. package/admin/js/templates/docs/tutorial-crud.html +68 -38
  23. package/admin/js/templates/docs/tutorial-forms.html +51 -35
  24. package/admin/js/templates/docs/tutorial-plugin.html +132 -56
  25. package/admin/js/templates/docs/usage-actions.html +55 -14
  26. package/admin/js/templates/docs/usage-collections.html +108 -0
  27. package/admin/js/templates/docs/usage-cta-shortcode.html +14 -3
  28. package/admin/js/templates/docs/usage-dconfig.html +0 -3
  29. package/admin/js/templates/docs/usage-editions.html +213 -0
  30. package/admin/js/templates/docs/usage-media.html +22 -6
  31. package/admin/js/templates/docs/usage-navigation.html +74 -18
  32. package/admin/js/templates/docs/usage-pages.html +60 -20
  33. package/admin/js/templates/docs/usage-plugins.html +89 -17
  34. package/admin/js/templates/docs/usage-shortcodes.html +123 -70
  35. package/admin/js/templates/docs/usage-site-settings.html +50 -18
  36. package/admin/js/templates/docs/usage-tools.html +73 -0
  37. package/admin/js/templates/docs/usage-users-roles.html +99 -20
  38. package/admin/js/templates/docs/usage-views.html +36 -19
  39. package/admin/js/templates/documentation.html +153 -32
  40. package/admin/js/templates/plugin-guide.html +15 -0
  41. package/admin/js/templates/plugin-guides.html +21 -0
  42. package/admin/js/templates/pro-docs.html +53 -234
  43. package/admin/js/templates/tutorials.html +5 -4
  44. package/admin/js/views/doc-pages.js +1 -1
  45. package/admin/js/views/index.js +1 -1
  46. package/admin/js/views/plugin-guides.js +5 -0
  47. package/bin/cli.js +6 -6
  48. package/package.json +1 -1
  49. package/plugins/blog/docs/guide.md +205 -0
  50. package/plugins/blog/plugin.json +1 -1
  51. package/plugins/feedback/docs/guide.md +95 -0
  52. package/plugins/feedback/plugin.json +1 -1
  53. package/plugins/free-tier.lock.json +16 -11
  54. package/plugins/mail-reader/docs/guide.md +147 -0
  55. package/plugins/mail-reader/plugin.json +1 -1
  56. package/plugins/security/docs/guide.md +170 -0
  57. package/plugins/security/plugin.json +1 -1
  58. package/plugins/shopping-cart/docs/guide.md +191 -0
  59. package/plugins/shopping-cart/plugin.json +1 -1
  60. package/server/routes/api/documentation.js +42 -0
  61. package/server/server.js +12 -0
  62. package/server/services/docs.js +13 -2
  63. package/server/services/pluginGuides.js +255 -0
  64. package/server/services/plugins.js +8 -0
@@ -69,133 +69,169 @@
69
69
  <div class="col-12">
70
70
  <div class="docs-body">
71
71
 
72
- <h3><span class="method-badge method-get">GET</span><span
73
- class="endpoint-path">/api/auth/setup-status</span></h3>
74
- <p class="auth-note">No authentication required.</p>
75
- <p>Check whether the CMS has been set up. Returns <code>{ needsSetup: true }</code> when no users
76
- exist.</p>
77
- <pre class="code-block"><code>// Response
78
- { "needsSetup": false }</code></pre>
79
-
80
- <h3><span class="method-badge method-post">POST</span><span
81
- class="endpoint-path">/api/auth/setup</span></h3>
82
- <p class="auth-note">No authentication required. Only succeeds when zero users exist.</p>
83
- <p>Create the initial admin account. Blocked once any user exists (returns 403).</p>
84
- <table class="table table-sm">
85
- <thead>
86
- <tr>
87
- <th>Field</th>
88
- <th>Type</th>
89
- <th>Description</th>
90
- </tr>
91
- </thead>
92
- <tbody>
93
- <tr>
94
- <td><code>name</code></td>
95
- <td>string</td>
96
- <td>Display name</td>
97
- </tr>
98
- <tr>
99
- <td><code>email</code></td>
100
- <td>string</td>
101
- <td>Email address</td>
102
- </tr>
103
- <tr>
104
- <td><code>password</code></td>
105
- <td>string</td>
106
- <td>Minimum 8 characters</td>
107
- </tr>
108
- </tbody>
109
- </table>
110
- <pre class="code-block"><code>// Response 201
111
- { "token": "eyJ...", "refreshToken": "eyJ...", "user": { "id": "...", "name": "...", "email": "...", "role": "admin" } }</code></pre>
112
-
113
- <h3><span class="method-badge method-post">POST</span><span
114
- class="endpoint-path">/api/auth/login</span></h3>
115
- <p class="auth-note">No authentication required.</p>
116
- <p>Authenticate with email and password. Returns access and refresh tokens.</p>
117
- <table class="table table-sm">
118
- <thead>
119
- <tr>
120
- <th>Field</th>
121
- <th>Type</th>
122
- <th>Description</th>
123
- </tr>
124
- </thead>
125
- <tbody>
126
- <tr>
127
- <td><code>email</code></td>
128
- <td>string</td>
129
- <td>User email</td>
130
- </tr>
131
- <tr>
132
- <td><code>password</code></td>
133
- <td>string</td>
134
- <td>User password</td>
135
- </tr>
136
- </tbody>
137
- </table>
138
- <pre class="code-block"><code>// Response 200
139
- { "token": "eyJ...", "refreshToken": "eyJ...", "user": { "id": "uuid", "name": "Alice", "email": "alice@example.com", "role": "admin" } }
140
-
72
+ <p>The admin signs in with a short-lived <strong>access token</strong> (sent as
73
+ <code>Authorization: Bearer &lt;token&gt;</code>) and a longer <strong>refresh token</strong> that gets a new
74
+ access token. The lifetimes are set in <code>config/auth.json</code> (default 15 minutes and 7 days). Each sign-in
75
+ is a <strong>session</strong> kept on disk, so signing out holds across a restart, and a new password ends the
76
+ account's other sessions. External programs should use an API token instead - see
77
+ <a href="#/docs/api/external">External API &amp; tokens</a>.</p>
78
+
79
+ <h3><span class="method-badge method-get">GET</span><span class="endpoint-path">/api/auth/setup-status</span></h3>
80
+ <p class="auth-note">No authentication required.</p>
81
+ <p>Whether the site still needs its first account, plus what the sign-in screen needs to know.</p>
82
+ <pre class="code-block"><code>// Response 200
83
+ { "needsSetup": false, "siteTitle": "My Site", "resetByEmail": true, "resetExpiresIn": "1 hour" }</code></pre>
84
+
85
+ <h3><span class="method-badge method-post">POST</span><span class="endpoint-path">/api/auth/setup</span></h3>
86
+ <p class="auth-note">No authentication required. Only succeeds while the site has no users.</p>
87
+ <p>Create the first account, as the level-0 role (<code>super-admin</code>), and sign it in. Refused with 403 once
88
+ any user exists.</p>
89
+ <table class="table table-sm">
90
+ <thead><tr><th>Field</th><th>Type</th><th>Description</th></tr></thead>
91
+ <tbody>
92
+ <tr><td><code>name</code></td><td>string</td><td>Display name</td></tr>
93
+ <tr><td><code>email</code></td><td>string</td><td>Email address</td></tr>
94
+ <tr><td><code>password</code></td><td>string</td><td>At least 8 characters, and whatever further rules a plugin
95
+ such as Security adds</td></tr>
96
+ </tbody>
97
+ </table>
98
+ <pre class="code-block"><code>// Response 201
99
+ { "token": "eyJ...", "refreshToken": "eyJ...", "user": { "id": "...", "name": "...", "email": "...", "role": "super-admin" } }
100
+ // Error 400 - a missing field, a bad email address or a password the rules refuse
101
+ { "error": "Password must be at least 8 characters" }</code></pre>
102
+
103
+ <h3><span class="method-badge method-post">POST</span><span class="endpoint-path">/api/auth/login</span></h3>
104
+ <p class="auth-note">No authentication required. Limited to 5 attempts a minute per address.</p>
105
+ <p>Sign in with email and password. Usually answers with tokens. When a plugin adds a second step (two-factor from
106
+ Security, for example) it answers with a <code>challenge</code> and a five-minute <code>ticket</code> instead,
107
+ which is completed at <code>/api/auth/login/verify</code>.</p>
108
+ <table class="table table-sm">
109
+ <thead><tr><th>Field</th><th>Type</th><th>Description</th></tr></thead>
110
+ <tbody>
111
+ <tr><td><code>email</code></td><td>string</td><td>User email</td></tr>
112
+ <tr><td><code>password</code></td><td>string</td><td>User password</td></tr>
113
+ </tbody>
114
+ </table>
115
+ <pre class="code-block"><code>// Response 200 - signed in
116
+ { "token": "eyJ...", "refreshToken": "eyJ...",
117
+ "user": { "id": "uuid", "name": "Alice", "email": "alice@example.com", "role": "admin", "additionalRoles": [], "level": 1 } }
118
+ // Response 200 - a second step is needed
119
+ { "challenge": { "kind": "code", "title": "Two-factor code", "label": "Code", "inputMode": "numeric", ... }, "ticket": "eyJ..." }
141
120
  // Error 401
142
121
  { "error": "Invalid credentials" }</code></pre>
143
-
144
- <h3><span class="method-badge method-get">GET</span><span class="endpoint-path">/api/auth/me</span>
145
- </h3>
146
- <p class="auth-note">Requires Bearer token.</p>
147
- <p>Return the authenticated user's profile.</p>
148
- <pre class="code-block"><code>// Response 200
149
- { "id": "uuid", "name": "Alice", "email": "alice@example.com", "role": "admin", "isActive": true }</code></pre>
150
-
151
- <h3><span class="method-badge method-post">POST</span><span
152
- class="endpoint-path">/api/auth/logout</span></h3>
153
- <p class="auth-note">No authentication required. Safe to call without a token.</p>
154
- <p>Blacklists the provided refresh token. The in-memory blacklist is cleared on server restart.</p>
155
- <table class="table table-sm">
156
- <thead>
157
- <tr>
158
- <th>Field</th>
159
- <th>Type</th>
160
- <th>Description</th>
161
- </tr>
162
- </thead>
163
- <tbody>
164
- <tr>
165
- <td><code>refreshToken</code></td>
166
- <td>string</td>
167
- <td>The refresh token to revoke (optional)</td>
168
- </tr>
169
- </tbody>
170
- </table>
171
- <pre class="code-block"><code>// Response 200
172
- { "ok": true }</code></pre>
173
-
174
- <h3><span class="method-badge method-post">POST</span><span
175
- class="endpoint-path">/api/auth/refresh</span></h3>
176
- <p class="auth-note">No authentication required. Provide a valid refresh token.</p>
177
- <p>Exchange a refresh token for a new access token.</p>
178
- <table class="table table-sm">
179
- <thead>
180
- <tr>
181
- <th>Field</th>
182
- <th>Type</th>
183
- <th>Description</th>
184
- </tr>
185
- </thead>
186
- <tbody>
187
- <tr>
188
- <td><code>refreshToken</code></td>
189
- <td>string</td>
190
- <td>A valid, non-revoked refresh token</td>
191
- </tr>
192
- </tbody>
193
- </table>
194
- <pre class="code-block"><code>// Response 200
122
+ <p>A successful sign-in also sets a session cookie, used only so the public site can recognise a signed-in visitor
123
+ (role-gated pages, draft previews). No API route accepts it in place of the Bearer token.</p>
124
+
125
+ <h3><span class="method-badge method-post">POST</span><span class="endpoint-path">/api/auth/login/verify</span></h3>
126
+ <p class="auth-note">No authentication required. Provide the ticket from <code>/api/auth/login</code>.</p>
127
+ <p>Answer a sign-in challenge. Five wrong answers end the ticket; sign in again.</p>
128
+ <table class="table table-sm">
129
+ <thead><tr><th>Field</th><th>Type</th><th>Description</th></tr></thead>
130
+ <tbody>
131
+ <tr><td><code>ticket</code></td><td>string</td><td>The ticket from the login response</td></tr>
132
+ <tr><td><code>response</code></td><td>string</td><td>The code (or recovery code)</td></tr>
133
+ <tr><td><code>mode</code></td><td>string</td><td><code>code</code> (default) or <code>recovery</code></td></tr>
134
+ <tr><td><code>trust</code></td><td>boolean</td><td>Remember this browser, where the challenge offers it</td></tr>
135
+ </tbody>
136
+ </table>
137
+ <pre class="code-block"><code>// Response 200 - the same as a successful /api/auth/login
138
+ // Error 401
139
+ { "error": "...", "triesLeft": 4 }</code></pre>
140
+
141
+ <h3><span class="method-badge method-post">POST</span><span class="endpoint-path">/api/auth/refresh</span></h3>
142
+ <p class="auth-note">No authentication required. Provide a valid refresh token.</p>
143
+ <p>Exchange a refresh token for a new access token. Refused once the session behind it has ended (signed out, a
144
+ password change, or the account made inactive).</p>
145
+ <pre class="code-block"><code>// Request body
146
+ { "refreshToken": "eyJ..." }
147
+ // Response 200
195
148
  { "token": "eyJ..." }
196
-
197
149
  // Error 401
198
- { "error": "Invalid or expired refresh token" }</code></pre>
150
+ { "error": "This session has ended. Sign in again." }</code></pre>
151
+
152
+ <h3><span class="method-badge method-post">POST</span><span class="endpoint-path">/api/auth/logout</span></h3>
153
+ <p class="auth-note">No authentication required. Safe to call without a token.</p>
154
+ <p>Ends the session the refresh token belongs to (sessions are stored on disk, so this survives a restart) and
155
+ clears the session cookie.</p>
156
+ <pre class="code-block"><code>// Request body (optional)
157
+ { "refreshToken": "eyJ..." }
158
+ // Response 200
159
+ { "ok": true }</code></pre>
160
+
161
+ <h3><span class="method-badge method-get">GET</span><span class="endpoint-path">/api/auth/me</span></h3>
162
+ <p class="auth-note">Requires Bearer token.</p>
163
+ <p>Your own account, with your profile fields (the <code>user-profiles</code> collection).</p>
164
+ <pre class="code-block"><code>// Response 200
165
+ { "id": "uuid", "name": "Alice", "email": "alice@example.com", "role": "admin", "additionalRoles": [],
166
+ "isActive": true, "profile": { "phone": "..." } }</code></pre>
167
+
168
+ <h3><span class="method-badge method-put">PUT</span><span class="endpoint-path">/api/auth/me</span></h3>
169
+ <p class="auth-note">Requires Bearer token.</p>
170
+ <p>Update your own name, email, password or profile (My Profile). A new password is checked against the site's
171
+ password rules and ends your other sessions. Your role cannot be changed here.</p>
172
+ <pre class="code-block"><code>// Request body - any of
173
+ { "name": "Alice B", "email": "alice@example.com", "password": "...", "profile": { "phone": "..." } }
174
+ // Response 200 - the updated account, as GET /api/auth/me
175
+ // Error 409 - the email is already used by another account</code></pre>
176
+
177
+ <h3><span class="method-badge method-post">POST</span><span class="endpoint-path">/api/auth/me/avatar</span></h3>
178
+ <p class="auth-note">Requires Bearer token.</p>
179
+ <p>Upload your picture (multipart, one file; JPEG, PNG, WebP or GIF, up to 8 MB). It is stored as a 256 px WebP.
180
+ <code>DELETE /api/auth/me/avatar</code> removes it. Pictures are served at
181
+ <code>GET /api/auth/avatar/:name</code>.</p>
182
+
183
+ <h3><span class="method-badge method-get">GET</span><span class="endpoint-path">/api/auth/permissions</span></h3>
184
+ <p class="auth-note">Requires Bearer token.</p>
185
+ <p>What you may do: the permissions of every role you hold combined (a bare name such as <code>pages</code> means every action on it; <code>resource.action</code> one action), whether your roles confine you to a few admin
186
+ screens (<code>adminScope</code>, <code>null</code> for the whole admin), and the screen the admin opens on.</p>
187
+ <pre class="code-block"><code>// Response 200
188
+ { "permissions": ["pages", "media", "contacts.read", ...], "adminScope": null, "adminHome": "#/" }</code></pre>
189
+ <p><code>GET /api/auth/permissions-registry</code> lists every permission the role editor can grant, including
190
+ those added by plugins.</p>
191
+
192
+ <h3><span class="method-badge method-get">GET</span><span class="endpoint-path">/api/auth/sessions</span></h3>
193
+ <p class="auth-note">Requires Bearer token.</p>
194
+ <p>Your sessions - where and when you are signed in - with the one you are using marked <code>current</code>.
195
+ <code>DELETE /api/auth/sessions/:sid</code> ends one; <code>POST /api/auth/sessions/revoke-others</code> signs you
196
+ out everywhere else.</p>
197
+ <pre class="code-block"><code>// Response 200
198
+ [ { "sid": "...", "createdAt": "...", "lastUsedAt": "...", "expiresAt": "...", "ip": "...", "ua": "...", "current": true } ]
199
+ // POST /api/auth/sessions/revoke-others - Response 200
200
+ { "ok": true, "ended": 2 }</code></pre>
201
+
202
+ <h3><span class="method-badge method-post">POST</span><span class="endpoint-path">/api/auth/forgot-password</span></h3>
203
+ <p class="auth-note">No authentication required. Limited to 3 requests an hour.</p>
204
+ <p>Ask for a password reset email. It answers at once and the same way whether or not the address has an account;
205
+ the email is sent afterwards, and at most 3 an hour go to any one account. The link is built on the Site URL
206
+ (Site Settings, General).</p>
207
+ <pre class="code-block"><code>// Request body
208
+ { "email": "alice@example.com" }
209
+ // Response 200
210
+ { "ok": true, "expiresIn": "1 hour" }</code></pre>
211
+
212
+ <h3><span class="method-badge method-post">POST</span><span class="endpoint-path">/api/auth/reset-password/check</span></h3>
213
+ <p class="auth-note">No authentication required. Provide the token from the reset link.</p>
214
+ <p>Is the link still good, and (with a <code>password</code>) what the password rules make of a new password as it
215
+ is typed.</p>
216
+ <pre class="code-block"><code>// Request body
217
+ { "token": "...", "password": "optional" }
218
+ // Response 200
219
+ { "email": "alice@example.com", "expiresAt": "...", "minutesLeft": 42, "problems": [] }
220
+ // Error 400
221
+ { "error": "Invalid or expired reset link", "expired": true }</code></pre>
222
+
223
+ <h3><span class="method-badge method-post">POST</span><span class="endpoint-path">/api/auth/reset-password</span></h3>
224
+ <p class="auth-note">No authentication required. Provide the token from the reset link.</p>
225
+ <p>Set a new password. The link is used up, every session of the account ends, and the account holder is emailed
226
+ that the password changed.</p>
227
+ <pre class="code-block"><code>// Request body
228
+ { "token": "...", "password": "new password" }
229
+ // Response 200
230
+ { "ok": true }
231
+ // Error 400
232
+ { "error": "Invalid or expired reset link", "expired": true }</code></pre>
233
+ <p>Administrators send, copy or withdraw reset links for other users with
234
+ <a href="#/docs/api/users">the Users API</a>.</p>
199
235
 
200
236
  </div>
201
237
  </div>
@@ -0,0 +1,197 @@
1
+ <div class="view-header">
2
+ <h1><span data-icon="code"></span> API · API Builder</h1>
3
+ <a href="#/api-reference" class="btn btn-ghost btn-sm"><span data-icon="arrow-left"></span> API overview</a>
4
+ </div>
5
+ <style>
6
+ .method-badge {
7
+ display: inline-block;
8
+ font-size: 11px;
9
+ font-weight: 700;
10
+ letter-spacing: 0.5px;
11
+ padding: 2px 7px;
12
+ border-radius: 4px;
13
+ font-family: var(--dm-font-mono, monospace);
14
+ margin-right: 6px;
15
+ vertical-align: middle;
16
+ }
17
+
18
+ .method-get {
19
+ background: color-mix(in srgb, var(--dm-success) 15%, transparent);
20
+ color: var(--dm-success);
21
+ }
22
+
23
+ .method-post {
24
+ background: color-mix(in srgb, var(--dm-primary) 15%, transparent);
25
+ color: var(--dm-primary);
26
+ }
27
+
28
+ .method-put {
29
+ background: color-mix(in srgb, var(--dm-warning) 15%, transparent);
30
+ color: var(--dm-warning-dark);
31
+ }
32
+
33
+ .method-patch {
34
+ background: color-mix(in srgb, var(--dm-info) 15%, transparent);
35
+ color: var(--dm-info);
36
+ }
37
+
38
+ .method-delete {
39
+ background: color-mix(in srgb, var(--dm-danger) 15%, transparent);
40
+ color: var(--dm-danger);
41
+ }
42
+
43
+ .endpoint-path {
44
+ font-family: var(--dm-font-mono, monospace);
45
+ font-size: 14px;
46
+ }
47
+
48
+ .auth-note {
49
+ font-size: 12px;
50
+ color: var(--dm-text-muted, #6b7280);
51
+ margin: 4px 0 12px;
52
+ }
53
+
54
+ .auth-note code {
55
+ font-size: 11px;
56
+ }
57
+
58
+ .docs-body h3 {
59
+ margin-top: 24px;
60
+ margin-bottom: 8px;
61
+ }
62
+
63
+ .docs-body h3:first-child {
64
+ margin-top: 0;
65
+ }
66
+ </style>
67
+
68
+
69
+ <div class="row">
70
+ <div class="col-12">
71
+ <div class="docs-body">
72
+
73
+ <p>API Builder publishes named, read-only endpoints over collection data at
74
+ <code>/api/x/&lt;project&gt;&lt;path&gt;</code> - for example <code>/api/x/world-cup/fixtures-day/:date</code>.
75
+ A definition is data, never code: it binds a path to one fixed collection query. An endpoint grants access to
76
+ exactly that query, whatever the collection's own <a href="#/docs/api/external">external API</a> settings say.</p>
77
+
78
+ <h2>How it works</h2>
79
+ <ul>
80
+ <li>Definitions are entries in the file-based <code>api-endpoints</code> preset collection, edited under
81
+ <strong>Data &gt; API Builder</strong> (permission family <code>api-endpoints.*</code>) or created by a
82
+ scaffolder recipe (<code>apiEndpoints: [...]</code>).</li>
83
+ <li>The first URL segment after <code>/api/x/</code> is the endpoint's project; the rest is matched against
84
+ its <code>path</code>. Static segments beat <code>:param</code> segments, so <code>/m/latest</code> wins
85
+ over <code>/m/:day</code>.</li>
86
+ <li>An endpoint may only expose a collection in its own project or in <code>core</code>, and never a
87
+ system-managed or preset collection (roles, users, API tokens and so on).</li>
88
+ <li>Only <code>GET</code> is served. An unknown project, unknown path or switched-off endpoint answers
89
+ <code>404</code> identically; so does every endpoint of a switched-off project.</li>
90
+ <li>Endpoints are project artefacts: a project that still has endpoints cannot be deleted.</li>
91
+ </ul>
92
+
93
+ <h2>The API Builder screen</h2>
94
+ <p><strong>Data &gt; API Builder</strong> lists the endpoints you can see (your project scope applies), with the
95
+ public ones counted on the sidebar badge. The editor has three tabs and a try-it console:</p>
96
+ <ul>
97
+ <li><strong>Definition</strong> - name, project (fixed once saved), path, collection, auth and mode.</li>
98
+ <li><strong>Query</strong> - filter rows (field, operator, value; a value can use a <code>:param</code> or a
99
+ query placeholder), sort, order and limit.</li>
100
+ <li><strong>Response fields</strong> - tick the fields the endpoint returns. None ticked returns every
101
+ field.</li>
102
+ <li><strong>Try it</strong> - calls the <em>saved</em> definition with the path parameters and query you enter,
103
+ keeps a history, and copies a request as curl. Save first to test a change. For a token endpoint, paste a
104
+ token.</li>
105
+ </ul>
106
+
107
+ <h2>Definition</h2>
108
+ <table class="table table-sm">
109
+ <thead>
110
+ <tr><th>Field</th><th>Default</th><th>Rules</th></tr>
111
+ </thead>
112
+ <tbody>
113
+ <tr><td><code>name</code></td><td>-</td><td>Required</td></tr>
114
+ <tr><td><code>project</code></td><td>-</td><td>Required, must exist; cannot be changed after creation</td></tr>
115
+ <tr><td><code>path</code></td><td>-</td><td>Starts with <code>/</code>; segments are lower-case letters, digits and hyphens, or <code>:name</code>. Two endpoints in one project cannot share a path shape (<code>/a/:x</code> and <code>/a/:y</code> collide - <code>409</code>).</td></tr>
116
+ <tr><td><code>collection</code></td><td>-</td><td>Required; own project or <code>core</code>; not system-managed</td></tr>
117
+ <tr><td><code>auth</code></td><td><code>public</code></td><td><code>public</code>, <code>token</code> or an existing role name</td></tr>
118
+ <tr><td><code>mode</code></td><td><code>list</code></td><td><code>list</code> returns <code>{ entries, total, page, limit }</code>; <code>single</code> returns the first match or <code>404</code></td></tr>
119
+ <tr><td><code>filter</code></td><td><code>{}</code></td><td><code>{ "field_op": value }</code> with the standard operators (<code>_eq</code>, <code>_ne</code>, <code>_gt</code>, <code>_gte</code>, <code>_lt</code>, <code>_lte</code>, <code>_in</code>, <code>_nin</code>, <code>_contains</code>, <code>_starts</code>, <code>_ends</code>, <code>_exists</code>). Values are strings, numbers or booleans.</td></tr>
120
+ <tr><td><code>sort</code> / <code>order</code></td><td><code>createdAt</code> / <code>desc</code></td><td>Order is <code>asc</code> or <code>desc</code></td></tr>
121
+ <tr><td><code>limit</code></td><td><code>50</code></td><td>Integer 0-500; <code>0</code> means no limit</td></tr>
122
+ <tr><td><code>fields</code></td><td><code>[]</code></td><td>Response allowlist of <code>data</code> fields; empty returns all</td></tr>
123
+ <tr><td><code>enabled</code></td><td><code>true</code></td><td>Off answers <code>404</code></td></tr>
124
+ </tbody>
125
+ </table>
126
+ <p>Filter values may carry placeholders:</p>
127
+ <ul>
128
+ <li><code>&#123;&#123;params.name&#125;&#125;</code> - from a <code>:name</code> path segment. It must exist in the path (checked on
129
+ save); empty at request time answers <code>400</code>.</li>
130
+ <li><code>&#123;&#123;query.name&#125;&#125;</code> - from <code>?name=</code>. When absent, the whole clause is dropped, so it works
131
+ as an optional refinement.</li>
132
+ </ul>
133
+ <pre class="code-block"><code class="language-json">{
134
+ "name": "Fixtures by day",
135
+ "project": "world-cup",
136
+ "path": "/fixtures-day/:date",
137
+ "collection": "wc-schedule",
138
+ "auth": "public",
139
+ "mode": "list",
140
+ "filter": { "date": "&#123;&#123;params.date&#125;&#125;", "stage": "&#123;&#123;query.stage&#125;&#125;" },
141
+ "sort": "seq", "order": "asc", "limit": 0,
142
+ "fields": ["seq", "date", "title", "stage"],
143
+ "enabled": true
144
+ }</code></pre>
145
+
146
+ <h2>Calling an endpoint</h2>
147
+ <p>List-mode endpoints also accept, within the definition's bounds:</p>
148
+ <table class="table table-sm">
149
+ <thead>
150
+ <tr><th>Query</th><th>Effect</th></tr>
151
+ </thead>
152
+ <tbody>
153
+ <tr><td><code>page</code></td><td>Page number</td></tr>
154
+ <tr><td><code>limit</code></td><td>Smaller pages; never above the definition's <code>limit</code> (or 500 when that is 0)</td></tr>
155
+ <tr><td><code>sort</code> / <code>order</code></td><td>Sort by any field the endpoint returns</td></tr>
156
+ <tr><td><code>&lt;field&gt;_&lt;op&gt;=value</code></td><td>Extra filters on fields the endpoint returns (no <code>filter[...]</code> wrapper). A field the definition already filters on cannot be overridden.</td></tr>
157
+ </tbody>
158
+ </table>
159
+ <pre class="code-block"><code class="language-bash"># Public, list mode
160
+ curl 'https://example.com/api/x/world-cup/fixtures-day/2026-06-11?stage=group&amp;limit=10'
161
+ # Token auth
162
+ curl -H 'Authorization: Bearer dcms_0123...cdef' \
163
+ https://example.com/api/x/world-cup/squad/eng
164
+ # Role auth - a user's access token
165
+ curl -H 'Authorization: Bearer eyJ...' https://example.com/api/x/members/directory</code></pre>
166
+ <p>Auth follows the same rules as the <a href="#/docs/api/external">external API</a>:</p>
167
+ <ul>
168
+ <li><strong>public</strong> - anyone. These responses are cached and refreshed automatically when the
169
+ collection's entries or the definition change.</li>
170
+ <li><strong>token</strong> - a project API token whose project is the endpoint's project. Scopes are checked
171
+ against the endpoint's collection with the <code>read</code> verb. A JWT is refused.</li>
172
+ <li><strong>role name</strong> - a signed-in user with that role or a more senior one.</li>
173
+ </ul>
174
+ <p>Errors: <code>401</code>/<code>403</code> from auth, <code>400</code> for a missing path parameter,
175
+ <code>404</code> for no such endpoint or no match in single mode.</p>
176
+
177
+ <h2>Managing endpoints over HTTP</h2>
178
+ <p>All need a JWT; project scope applies to the endpoint and to the collection it queries (<code>403</code>
179
+ otherwise).</p>
180
+ <table class="table table-sm">
181
+ <thead>
182
+ <tr><th>Method</th><th>Path</th><th>Permission</th><th>Description</th></tr>
183
+ </thead>
184
+ <tbody>
185
+ <tr><td><code>GET</code></td><td><code>/api/api-endpoints</code></td><td><code>api-endpoints.read</code></td><td>List definitions</td></tr>
186
+ <tr><td><code>GET</code></td><td><code>/api/api-endpoints/:id</code></td><td><code>api-endpoints.read</code></td><td>One definition</td></tr>
187
+ <tr><td><code>POST</code></td><td><code>/api/api-endpoints</code></td><td><code>api-endpoints.create</code></td><td>Create; <code>201</code>, <code>400</code> invalid, <code>409</code> duplicate path shape</td></tr>
188
+ <tr><td><code>PUT</code></td><td><code>/api/api-endpoints/:id</code></td><td><code>api-endpoints.update</code></td><td>Update the fields sent; <code>project</code> is ignored</td></tr>
189
+ <tr><td><code>DELETE</code></td><td><code>/api/api-endpoints/:id</code></td><td><code>api-endpoints.delete</code></td><td>Delete; the URL answers <code>404</code> at once</td></tr>
190
+ </tbody>
191
+ </table>
192
+ <p>The base <code>super-admin</code> and <code>admin</code> roles hold <code>api-endpoints.*</code>; custom roles
193
+ need it granted in the role editor.</p>
194
+
195
+ </div>
196
+ </div>
197
+ </div>