Skip to content

Commit c84d786

Browse files
dnplkndllclaude
andcommitted
feat: add REST API documentation to API Tokens settings page
Add a collapsible documentation section below the token list that shows available REST API endpoints, base URL, and a curl example. Helps users understand how to use their tokens for automation and integrations. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> Signed-off-by: Don Kendall <kendall@donkendall.com>
1 parent 2f41a40 commit c84d786

3 files changed

Lines changed: 222 additions & 3 deletions

File tree

plugins/setting-assets/lang/en.json

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -231,6 +231,16 @@
231231
"ApiTokenWorkspace": "Workspace",
232232
"Created": "Created",
233233
"Expires": "Expires",
234-
"TokenStatus": "Status"
234+
"TokenStatus": "Status",
235+
"ApiUsageTitle": "Using the REST API",
236+
"ApiUsageDescription": "Use your API token with the built-in REST API to query and modify workspace data. Pass the token as a Bearer token in the Authorization header.",
237+
"ApiEndpointPing": "Health check",
238+
"ApiEndpointFindAll": "Query documents by class",
239+
"ApiEndpointFindAllPost": "Query with filters (JSON body)",
240+
"ApiEndpointTx": "Create or update documents",
241+
"ApiEndpointLoadModel": "Load the data model",
242+
"ApiEndpointAccount": "Get account info",
243+
"ApiBaseUrl": "Base URL",
244+
"ApiWorkspaceId": "Your workspace ID (UUID) is included in the token. Pass it as :workspaceId in the URL."
235245
}
236246
}

plugins/setting-resources/src/components/ApiTokens.svelte

Lines changed: 200 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
Scroller,
2424
showPopup
2525
} from '@hcengineering/ui'
26-
import { MessageBox } from '@hcengineering/presentation'
26+
import { MessageBox, copyTextToClipboard } from '@hcengineering/presentation'
2727
import setting from '@hcengineering/setting'
2828
import { type ApiTokenInfo } from '@hcengineering/account-client'
2929
import { getAccountClient } from '../utils'
@@ -32,6 +32,15 @@
3232
3333
let loading = true
3434
let tokens: ApiTokenInfo[] = []
35+
let showApiDocs = false
36+
37+
function getBaseUrl (): string {
38+
return window.location.origin
39+
}
40+
41+
async function copySnippet (text: string): Promise<void> {
42+
await copyTextToClipboard(text)
43+
}
3544
3645
function loadTokens (): void {
3746
loading = true
@@ -149,6 +158,70 @@
149158
</table>
150159
</Scroller>
151160
{/if}
161+
162+
<div class="api-docs-section">
163+
<!-- svelte-ignore a11y-click-events-have-key-events -->
164+
<!-- svelte-ignore a11y-no-static-element-interactions -->
165+
<div class="api-docs-toggle" on:click={() => { showApiDocs = !showApiDocs }}>
166+
<span class="api-docs-arrow" class:expanded={showApiDocs}>&#9654;</span>
167+
<Label label={setting.string.ApiUsageTitle} />
168+
</div>
169+
{#if showApiDocs}
170+
<div class="api-docs-content">
171+
<p class="api-docs-desc"><Label label={setting.string.ApiUsageDescription} /></p>
172+
173+
<div class="api-docs-block">
174+
<span class="api-docs-label"><Label label={setting.string.ApiBaseUrl} /></span>
175+
<!-- svelte-ignore a11y-click-events-have-key-events -->
176+
<!-- svelte-ignore a11y-no-static-element-interactions -->
177+
<code class="api-docs-code clickable" on:click={() => copySnippet(getBaseUrl() + '/_transactor/api/v1')}>{getBaseUrl()}/_transactor/api/v1</code>
178+
</div>
179+
180+
<p class="api-docs-note"><Label label={setting.string.ApiWorkspaceId} /></p>
181+
182+
<div class="api-docs-endpoints">
183+
<div class="api-docs-endpoint">
184+
<div class="api-docs-method get">GET</div>
185+
<code>/api/v1/ping/:workspaceId</code>
186+
<span class="api-docs-endpoint-desc"><Label label={setting.string.ApiEndpointPing} /></span>
187+
</div>
188+
<div class="api-docs-endpoint">
189+
<div class="api-docs-method get">GET</div>
190+
<code>/api/v1/find-all/:workspaceId?class=...</code>
191+
<span class="api-docs-endpoint-desc"><Label label={setting.string.ApiEndpointFindAll} /></span>
192+
</div>
193+
<div class="api-docs-endpoint">
194+
<div class="api-docs-method post">POST</div>
195+
<code>/api/v1/find-all/:workspaceId</code>
196+
<span class="api-docs-endpoint-desc"><Label label={setting.string.ApiEndpointFindAllPost} /></span>
197+
</div>
198+
<div class="api-docs-endpoint">
199+
<div class="api-docs-method post">POST</div>
200+
<code>/api/v1/tx/:workspaceId</code>
201+
<span class="api-docs-endpoint-desc"><Label label={setting.string.ApiEndpointTx} /></span>
202+
</div>
203+
<div class="api-docs-endpoint">
204+
<div class="api-docs-method get">GET</div>
205+
<code>/api/v1/load-model/:workspaceId</code>
206+
<span class="api-docs-endpoint-desc"><Label label={setting.string.ApiEndpointLoadModel} /></span>
207+
</div>
208+
<div class="api-docs-endpoint">
209+
<div class="api-docs-method get">GET</div>
210+
<code>/api/v1/account/:workspaceId</code>
211+
<span class="api-docs-endpoint-desc"><Label label={setting.string.ApiEndpointAccount} /></span>
212+
</div>
213+
</div>
214+
215+
<div class="api-docs-example">
216+
<span class="api-docs-label">Example</span>
217+
<!-- svelte-ignore a11y-click-events-have-key-events -->
218+
<!-- svelte-ignore a11y-no-static-element-interactions -->
219+
<pre class="api-docs-pre clickable" on:click={() => copySnippet(`curl -H "Authorization: Bearer YOUR_TOKEN" \\\n "${getBaseUrl()}/_transactor/api/v1/find-all/WORKSPACE_ID?class=tracker:class:Project"`)}>{`curl -H "Authorization: Bearer YOUR_TOKEN" \\
220+
"${getBaseUrl()}/_transactor/api/v1/find-all/WORKSPACE_ID?class=tracker:class:Project"`}</pre>
221+
</div>
222+
</div>
223+
{/if}
224+
</div>
152225
</div>
153226
</div>
154227
</div>
@@ -175,4 +248,130 @@
175248
background-color: var(--tag-accent-FlamingoColor);
176249
color: var(--tag-on-accent-FlamingoColor);
177250
}
251+
252+
.api-docs-section {
253+
margin-top: 2rem;
254+
border-top: 1px solid var(--theme-popup-divider);
255+
padding-top: 1rem;
256+
}
257+
.api-docs-toggle {
258+
display: flex;
259+
align-items: center;
260+
gap: 0.5rem;
261+
cursor: pointer;
262+
font-weight: 500;
263+
font-size: 0.875rem;
264+
color: var(--theme-content-color);
265+
user-select: none;
266+
}
267+
.api-docs-arrow {
268+
display: inline-block;
269+
font-size: 0.625rem;
270+
transition: transform 0.15s ease;
271+
color: var(--theme-dark-color);
272+
}
273+
.api-docs-arrow.expanded {
274+
transform: rotate(90deg);
275+
}
276+
.api-docs-content {
277+
margin-top: 1rem;
278+
display: flex;
279+
flex-direction: column;
280+
gap: 0.75rem;
281+
}
282+
.api-docs-desc {
283+
font-size: 0.8125rem;
284+
color: var(--theme-dark-color);
285+
line-height: 1.5;
286+
}
287+
.api-docs-note {
288+
font-size: 0.75rem;
289+
color: var(--theme-dark-color);
290+
font-style: italic;
291+
}
292+
.api-docs-block {
293+
display: flex;
294+
flex-direction: column;
295+
gap: 0.25rem;
296+
}
297+
.api-docs-label {
298+
font-size: 0.75rem;
299+
font-weight: 500;
300+
color: var(--theme-dark-color);
301+
}
302+
.api-docs-code {
303+
background: var(--theme-popup-color);
304+
border: 1px solid var(--theme-popup-divider);
305+
border-radius: 0.375rem;
306+
padding: 0.375rem 0.625rem;
307+
font-family: var(--mono-font);
308+
font-size: 0.75rem;
309+
color: var(--theme-content-color);
310+
width: fit-content;
311+
}
312+
.api-docs-endpoints {
313+
display: flex;
314+
flex-direction: column;
315+
gap: 0.375rem;
316+
}
317+
.api-docs-endpoint {
318+
display: flex;
319+
align-items: center;
320+
gap: 0.5rem;
321+
font-size: 0.75rem;
322+
323+
code {
324+
font-family: var(--mono-font);
325+
color: var(--theme-content-color);
326+
}
327+
}
328+
.api-docs-endpoint-desc {
329+
color: var(--theme-dark-color);
330+
font-size: 0.6875rem;
331+
}
332+
.api-docs-method {
333+
display: inline-flex;
334+
align-items: center;
335+
justify-content: center;
336+
min-width: 2.75rem;
337+
padding: 0.125rem 0.375rem;
338+
border-radius: 0.25rem;
339+
font-size: 0.625rem;
340+
font-weight: 600;
341+
font-family: var(--mono-font);
342+
text-transform: uppercase;
343+
}
344+
.api-docs-method.get {
345+
background-color: var(--tag-accent-PorpoiseColor);
346+
color: var(--tag-on-accent-PorpoiseColor);
347+
}
348+
.api-docs-method.post {
349+
background-color: var(--tag-accent-SunshineColor);
350+
color: var(--tag-on-accent-SunshineColor);
351+
}
352+
.api-docs-example {
353+
display: flex;
354+
flex-direction: column;
355+
gap: 0.25rem;
356+
margin-top: 0.25rem;
357+
}
358+
.api-docs-pre {
359+
background: var(--theme-popup-color);
360+
border: 1px solid var(--theme-popup-divider);
361+
border-radius: 0.375rem;
362+
padding: 0.625rem 0.75rem;
363+
font-family: var(--mono-font);
364+
font-size: 0.6875rem;
365+
line-height: 1.6;
366+
color: var(--theme-content-color);
367+
white-space: pre-wrap;
368+
word-break: break-all;
369+
overflow-x: auto;
370+
}
371+
.clickable {
372+
cursor: pointer;
373+
&:hover {
374+
border-color: var(--theme-button-hovered);
375+
}
376+
}
178377
</style>

plugins/setting/src/index.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -351,7 +351,17 @@ export default plugin(settingId, {
351351
ApiTokenWorkspace: '' as IntlString,
352352
Created: '' as IntlString,
353353
Expires: '' as IntlString,
354-
TokenStatus: '' as IntlString
354+
TokenStatus: '' as IntlString,
355+
ApiUsageTitle: '' as IntlString,
356+
ApiUsageDescription: '' as IntlString,
357+
ApiEndpointPing: '' as IntlString,
358+
ApiEndpointFindAll: '' as IntlString,
359+
ApiEndpointFindAllPost: '' as IntlString,
360+
ApiEndpointTx: '' as IntlString,
361+
ApiEndpointLoadModel: '' as IntlString,
362+
ApiEndpointAccount: '' as IntlString,
363+
ApiBaseUrl: '' as IntlString,
364+
ApiWorkspaceId: '' as IntlString
355365
},
356366
icon: {
357367
AccountSettings: '' as Asset,

0 commit comments

Comments
 (0)