@proveanything/smartlinks 1.6.7 → 1.7.1

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 (41) hide show
  1. package/dist/api/attestation.d.ts +22 -0
  2. package/dist/api/attestation.js +22 -0
  3. package/dist/api/attestations.d.ts +292 -0
  4. package/dist/api/attestations.js +405 -0
  5. package/dist/api/containers.d.ts +236 -0
  6. package/dist/api/containers.js +316 -0
  7. package/dist/api/index.d.ts +2 -0
  8. package/dist/api/index.js +2 -0
  9. package/dist/api/tags.d.ts +20 -1
  10. package/dist/api/tags.js +30 -0
  11. package/dist/docs/API_SUMMARY.md +711 -9
  12. package/dist/docs/app-manifest.md +430 -0
  13. package/dist/docs/attestations.md +498 -0
  14. package/dist/docs/container-tracking.md +437 -0
  15. package/dist/docs/deep-link-discovery.md +6 -6
  16. package/dist/docs/executor.md +554 -0
  17. package/dist/docs/interactions.md +291 -0
  18. package/dist/docs/manifests.md +200 -0
  19. package/dist/docs/mpa.md +135 -0
  20. package/dist/docs/overview.md +372 -0
  21. package/dist/index.d.ts +3 -0
  22. package/dist/openapi.yaml +3110 -1323
  23. package/dist/types/appManifest.d.ts +159 -2
  24. package/dist/types/attestation.d.ts +12 -0
  25. package/dist/types/attestations.d.ts +237 -0
  26. package/dist/types/attestations.js +11 -0
  27. package/dist/types/containers.d.ts +186 -0
  28. package/dist/types/containers.js +10 -0
  29. package/dist/types/tags.d.ts +47 -3
  30. package/docs/API_SUMMARY.md +711 -9
  31. package/docs/app-manifest.md +430 -0
  32. package/docs/attestations.md +498 -0
  33. package/docs/container-tracking.md +437 -0
  34. package/docs/deep-link-discovery.md +6 -6
  35. package/docs/executor.md +554 -0
  36. package/docs/interactions.md +291 -0
  37. package/docs/manifests.md +200 -0
  38. package/docs/mpa.md +135 -0
  39. package/docs/overview.md +372 -0
  40. package/openapi.yaml +3110 -1323
  41. package/package.json +1 -1
@@ -0,0 +1,554 @@
1
+ # SmartLinks Executor Model
2
+
3
+ > **SDK minimum:** `@proveanything/smartlinks@1.4.1`
4
+
5
+ ---
6
+
7
+ ## What Is an Executor?
8
+
9
+ An executor is a **standalone JavaScript library** (`executor.umd.js` / `executor.es.js`) that a SmartLinks app ships alongside its widget and container bundles. It exposes programmatic functions that external systems — AI orchestrators, the Hub server, setup wizards — can call **without rendering the app's UI**.
10
+
11
+ Every app can optionally ship an executor. The executor pattern solves three problems that iframe-based apps can't address on their own:
12
+
13
+ | Problem | Executor Solution |
14
+ |---------|-------------------|
15
+ | AI orchestrators need to configure apps programmatically | Executor exposes typed mutation functions |
16
+ | Search engines can't execute JavaScript in iframes | `getSEO()` returns metadata the server injects into `<head>` |
17
+ | AI crawlers need readable content, not interactive widgets | `getLLMContent()` returns structured markdown sections |
18
+
19
+ ### What an Executor Is Not
20
+
21
+ - **Not a REST API** — it's a JS bundle loaded and called in-process (Node.js or browser)
22
+ - **Not required** — apps work fine without one; the executor adds programmatic capabilities
23
+ - **Not the app itself** — it shares types and logic with the app but produces a separate bundle
24
+
25
+ ---
26
+
27
+ ## Manifest Declaration
28
+
29
+ Every executor is declared in `app.manifest.json` so the platform can discover and load it:
30
+
31
+ ```json
32
+ {
33
+ "meta": {
34
+ "name": "My App",
35
+ "appId": "my-app",
36
+ "version": "1.0.0",
37
+ "seo": { "priority": 10 }
38
+ },
39
+ "executor": {
40
+ "files": {
41
+ "js": { "umd": "dist/executor.umd.js", "esm": "dist/executor.es.js" }
42
+ },
43
+ "factory": "createMyAppExecutor",
44
+ "exports": ["createMyAppExecutor", "getSEO", "getLLMContent"],
45
+ "description": "Programmatic configuration and SEO API for My App."
46
+ }
47
+ }
48
+ ```
49
+
50
+ | Field | Purpose |
51
+ |-------|---------|
52
+ | `files.js.umd` | UMD bundle path (loaded via `<script>` tag — available as `window.MyAppExecutor`) |
53
+ | `files.js.esm` | ESM bundle path (for `import()` in modern environments) |
54
+ | `factory` | Name of the factory function that creates an executor instance |
55
+ | `exports` | All named exports — tells consumers what's available without loading the bundle |
56
+ | `description` | Human-readable summary for AI orchestrators and admin UIs |
57
+
58
+ ---
59
+
60
+ ## Loading an Executor
61
+
62
+ ```typescript
63
+ // ESM (modern bundlers, Deno, Node 18+)
64
+ const { createMyAppExecutor } = await import('https://my-app.smartlinks.app/dist/executor.es.js');
65
+
66
+ // UMD (script tag, legacy environments)
67
+ // After loading executor.umd.js:
68
+ const { createMyAppExecutor } = window.MyAppExecutor;
69
+ ```
70
+
71
+ ### Initialising
72
+
73
+ Every executor factory receives a standard `ExecutorContext`:
74
+
75
+ ```typescript
76
+ import * as SL from '@proveanything/smartlinks';
77
+
78
+ const executor = createMyAppExecutor({
79
+ collectionId: 'col_abc123',
80
+ appId: 'my-app',
81
+ SL, // Pre-initialised SmartLinks SDK
82
+ });
83
+ ```
84
+
85
+ The SDK reference is passed in rather than imported because executors run in contexts where the SDK may already be loaded (the Hub server, an AI orchestrator's runtime, a parent app). This avoids duplicate SDK instances and ensures the caller controls authentication.
86
+
87
+ ---
88
+
89
+ ## Core Pattern: The Mutate Pipeline
90
+
91
+ The recommended internal architecture for executor mutations is a `mutate()` helper:
92
+
93
+ ```typescript
94
+ async function mutate(fn: (config: MyConfig) => MyConfig): Promise<MyConfig> {
95
+ const current = await getConfig(); // Read current state + merge with defaults
96
+ const updated = fn(current); // Pure transformation (no side effects)
97
+ await saveConfig(updated); // Validate + write via SL API
98
+ return updated;
99
+ }
100
+ ```
101
+
102
+ This gives you:
103
+ - **Read-before-write** — always operates on the latest config
104
+ - **Pure transformations** — mutation functions are testable without API calls
105
+ - **Validation gate** — invalid configs are rejected before saving
106
+ - **`admin: true` encapsulated** — callers don't need to remember the flag
107
+
108
+ All transformation functions should be pure: `(Config) → Config`. Side effects (SL API calls) are isolated in the factory's `getConfig`/`saveConfig` wrappers.
109
+
110
+ ---
111
+
112
+ ## SEO Contract
113
+
114
+ The SEO contract allows the Hub server (or any SSR layer) to generate metadata for search engines and social previews **without rendering the app's UI**.
115
+
116
+ ### Why This Exists
117
+
118
+ SmartLinks apps run in iframes — search engines and social crawlers can't see their content. The server needs a way to ask each app: "What title, description, and structured data should this page have?"
119
+
120
+ Rather than HTTP endpoints (which add latency and infrastructure), the server loads the app's executor bundle in Node.js and calls `getSEO()` directly. This is fast (~1ms), requires no network round-trips, and uses pre-fetched entity data.
121
+
122
+ ### How It Works
123
+
124
+ ```text
125
+ 1. Server receives a page request
126
+ 2. Loads executor.umd.js for each app on the page (Node.js)
127
+ 3. Calls getSEO() with pre-fetched collection / product / proof data
128
+ 4. Merges results from all apps using priority-based resolution
129
+ 5. Injects into <head>: <title>, OG tags, JSON-LD, canonical URL
130
+ 6. Falls back to defaults if any app doesn't respond within 200ms
131
+ ```
132
+
133
+ ### Input
134
+
135
+ ```typescript
136
+ interface SEOInput {
137
+ collectionId: string;
138
+ appId: string;
139
+ productId?: string;
140
+ proofId?: string;
141
+ /** Pre-authenticated SmartLinks SDK for any extra lookups */
142
+ SL: typeof import('@proveanything/smartlinks');
143
+ /** Pre-fetched objects — use these directly, avoid additional SL calls */
144
+ collection?: Record<string, any>;
145
+ product?: Record<string, any>;
146
+ proof?: Record<string, any>;
147
+ }
148
+ ```
149
+
150
+ **Key principle:** The server pre-fetches collection, product, and proof data and passes them in. Your `getSEO()` should use these directly — **avoid making additional SL API calls** unless you need app-specific data (e.g., your app's config). This keeps execution well within the 200ms budget.
151
+
152
+ ### Output
153
+
154
+ ```typescript
155
+ interface SEOResult {
156
+ /** Page title — singular field, highest-priority app wins */
157
+ title?: string;
158
+ /** Meta description — singular */
159
+ description?: string;
160
+ /** Open Graph image URL — singular */
161
+ ogImage?: string;
162
+ /** JSON-LD structured data — additive, merged from all apps */
163
+ jsonLd?: Record<string, any> | Record<string, any>[];
164
+ /** Plain text summary for AI crawlers — additive, concatenated */
165
+ contentSummary?: string;
166
+ /** Topic tags — additive, merged and deduplicated */
167
+ topics?: string[];
168
+ }
169
+ ```
170
+
171
+ ### Multi-App Priority Merging
172
+
173
+ When multiple apps appear on the same page, the server merges their SEO output:
174
+
175
+ | Field | Strategy | Rule |
176
+ |-------|----------|------|
177
+ | `title` | **Singular** | Highest `meta.seo.priority` wins. Equal priority: first app in page order. |
178
+ | `description` | **Singular** | Same as title |
179
+ | `ogImage` | **Singular** | Same as title |
180
+ | `jsonLd` | **Additive** | All apps' JSON-LD arrays are concatenated |
181
+ | `contentSummary` | **Additive** | Concatenated from all apps |
182
+ | `topics` | **Additive** | Merged and deduplicated from all apps |
183
+
184
+ Set your app's priority in `app.manifest.json` (default is `0`, higher wins):
185
+
186
+ ```json
187
+ "meta": {
188
+ "seo": { "priority": 10 }
189
+ }
190
+ ```
191
+
192
+ ### Implementation Example
193
+
194
+ ```typescript
195
+ // src/executor/seo.ts — Warranty app example
196
+ export function getSEO(input: SEOInput): SEOResult {
197
+ const { product, collection } = input;
198
+ const brandName = collection?.name ?? 'Product';
199
+
200
+ return {
201
+ title: product
202
+ ? `Warranty — ${product.name}`
203
+ : 'Warranty Registration',
204
+ description: `Register your ${product?.name ?? 'product'} warranty with ${brandName}.`,
205
+ jsonLd: {
206
+ '@context': 'https://schema.org',
207
+ '@type': 'WebPage',
208
+ name: `Warranty — ${product?.name ?? brandName}`,
209
+ description: `Warranty registration for ${product?.name ?? 'products'}.`,
210
+ },
211
+ topics: ['Warranty', 'Product Registration'],
212
+ };
213
+ }
214
+ ```
215
+
216
+ ### Declaring SEO in the Manifest
217
+
218
+ ```json
219
+ "meta": {
220
+ "seo": {
221
+ "strategy": "executor",
222
+ "priority": 10,
223
+ "contract": {
224
+ "function": "getSEO",
225
+ "input": ["collectionId", "appId", "productId", "proofId", "collection", "product", "proof", "SL"],
226
+ "timeout": 200,
227
+ "responseShape": {
228
+ "title": "string",
229
+ "description": "string",
230
+ "ogImage": "string (URL)",
231
+ "jsonLd": "object | object[]",
232
+ "contentSummary": "string",
233
+ "topics": "string[]"
234
+ }
235
+ }
236
+ }
237
+ }
238
+ ```
239
+
240
+ ---
241
+
242
+ ## LLM Content Contract
243
+
244
+ The `getLLMContent()` function generates structured markdown sections for AI crawler consumption. Unlike `getSEO()` (which produces HTML meta tags), this produces **readable content** — rich text that an LLM can ingest to understand what the page is about.
245
+
246
+ ### Why This Exists
247
+
248
+ AI crawlers (GPTBot, ClaudeBot, PerplexityBot, etc.) want to understand page content, not just metadata. Since microapp content lives inside iframes that crawlers can't execute, each app provides a way to say: "Here's my content as readable markdown."
249
+
250
+ ### Tiered AI Crawler Strategy
251
+
252
+ | Tier | Mechanism | Purpose |
253
+ |------|-----------|---------|
254
+ | **Discovery** | `/llms.txt` at site root | Table of contents — lists pages, products, links |
255
+ | **Per-route rendering** | Bot User-Agent detection | Rich semantic HTML injected before React root |
256
+ | **App-level sections** | `getLLMContent()` on each app's executor | Structured markdown sections merged by server |
257
+
258
+ AI crawlers naturally follow links from `/llms.txt` to individual pages. When they hit a page, the server detects the bot User-Agent, calls each embedded app's `getLLMContent()`, and injects the combined content as HTML that the crawler can read.
259
+
260
+ **Detected bots:**
261
+ ```
262
+ GPTBot, ChatGPT-User, PerplexityBot, ClaudeBot, Claude-Web,
263
+ Google-Extended, CCBot, Amazonbot, anthropic-ai, Bytespider, cohere-ai
264
+ ```
265
+
266
+ ### Input
267
+
268
+ ```typescript
269
+ interface LLMContentInput {
270
+ collectionId: string;
271
+ appId: string;
272
+ productId?: string;
273
+ proofId?: string;
274
+ SL: typeof import('@proveanything/smartlinks');
275
+ collection?: Record<string, any>;
276
+ product?: Record<string, any>;
277
+ proof?: Record<string, any>;
278
+ /** Which page slug is being rendered */
279
+ pageSlug?: string;
280
+ }
281
+ ```
282
+
283
+ ### Output
284
+
285
+ ```typescript
286
+ interface LLMContentResult {
287
+ sections: LLMContentSection[];
288
+ }
289
+
290
+ interface LLMContentSection {
291
+ /** Section heading (e.g., "FAQ", "Warranty", "Product Manual") */
292
+ heading: string;
293
+ /** Markdown content */
294
+ content: string;
295
+ /** Sort order — lower numbers appear first (default: 100) */
296
+ order?: number;
297
+ }
298
+ ```
299
+
300
+ ### Implementation Example
301
+
302
+ ```typescript
303
+ // src/executor/llm-content.ts — FAQ app example
304
+ export async function getLLMContent(input: LLMContentInput): Promise<LLMContentResult> {
305
+ const { collectionId, appId, product, SL } = input;
306
+
307
+ const config = await SL.appConfiguration.getConfig({ collectionId, appId });
308
+ const faqs = config?.faqs ?? [];
309
+
310
+ if (faqs.length === 0) return { sections: [] };
311
+
312
+ const content = faqs
313
+ .map((faq: any) => `### ${faq.question}\n\n${faq.answer}`)
314
+ .join('\n\n');
315
+
316
+ return {
317
+ sections: [
318
+ {
319
+ heading: product
320
+ ? `${product.name} — Frequently Asked Questions`
321
+ : 'Frequently Asked Questions',
322
+ content,
323
+ order: 50,
324
+ },
325
+ ],
326
+ };
327
+ }
328
+ ```
329
+
330
+ ### Declaring LLM Content in the Manifest
331
+
332
+ ```json
333
+ "executor": {
334
+ "exports": ["createExecutor", "getSEO", "getLLMContent"],
335
+ "llmContent": {
336
+ "function": "getLLMContent",
337
+ "timeout": 500,
338
+ "responseShape": {
339
+ "sections": "Array<{ heading: string, content: string, order?: number }>"
340
+ }
341
+ }
342
+ }
343
+ ```
344
+
345
+ Note the longer timeout (500ms vs 200ms for SEO) — LLM content can involve fetching app-specific data and is only served to bots, so slightly higher latency is acceptable.
346
+
347
+ ---
348
+
349
+ ## Building an Executor
350
+
351
+ ### File Structure
352
+
353
+ ```
354
+ src/executor/
355
+ ├── index.ts # Factory function + re-exports
356
+ ├── types.ts # Public TypeScript types
357
+ ├── seo.ts # getSEO implementation
358
+ ├── llm-content.ts # getLLMContent implementation
359
+ └── ... # App-specific mutation modules
360
+ ```
361
+
362
+ ### Vite Config
363
+
364
+ Create `vite.config.executor.ts`:
365
+
366
+ ```typescript
367
+ import { defineConfig } from 'vite';
368
+ import path from 'path';
369
+
370
+ const ENABLE = process.env.VITE_ENABLE_EXECUTOR !== 'false';
371
+
372
+ export default defineConfig({
373
+ resolve: {
374
+ alias: { '@': path.resolve(__dirname, './src') },
375
+ },
376
+ build: {
377
+ outDir: 'dist',
378
+ emptyOutDir: false, // Append to existing dist/ — run after main builds
379
+ lib: {
380
+ entry: ENABLE
381
+ ? path.resolve(__dirname, 'src/executor/index.ts')
382
+ : path.resolve(__dirname, 'src/executor-stub.ts'),
383
+ name: 'MyAppExecutor', // window.MyAppExecutor for UMD
384
+ formats: ['umd', 'es'],
385
+ fileName: (format) => `executor.${format}.js`,
386
+ },
387
+ rollupOptions: {
388
+ external: ['@proveanything/smartlinks'],
389
+ output: {
390
+ globals: { '@proveanything/smartlinks': 'SL' },
391
+ },
392
+ },
393
+ },
394
+ });
395
+ ```
396
+
397
+ **Key points:**
398
+ - `emptyOutDir: false` — the executor build appends to `dist/` after the main app builds
399
+ - Only `@proveanything/smartlinks` is externalised — all other dependencies are bundled
400
+ - `VITE_ENABLE_EXECUTOR=false` produces a stub, allowing CI to skip executor builds when not yet implemented
401
+
402
+ ### Build Script
403
+
404
+ ```json
405
+ {
406
+ "scripts": {
407
+ "build": "vite build && vite build --config vite.config.widget.ts && vite build --config vite.config.container.ts && vite build --config vite.config.executor.ts && node scripts/hash-bundles.mjs",
408
+ "build:executor": "vite build --config vite.config.executor.ts"
409
+ }
410
+ }
411
+ ```
412
+
413
+ ### Stub File
414
+
415
+ When the executor build is disabled, export an empty object:
416
+
417
+ ```typescript
418
+ // src/executor-stub.ts
419
+ export {};
420
+ ```
421
+
422
+ ---
423
+
424
+ ## Minimal Executor (SEO + LLM only)
425
+
426
+ Not every app needs full programmatic configuration. If you just want SEO and LLM support, the executor can be very small:
427
+
428
+ ```typescript
429
+ // src/executor/index.ts
430
+ import type { SEOInput, SEOResult, LLMContentInput, LLMContentResult } from './types';
431
+
432
+ export function getSEO(input: SEOInput): SEOResult {
433
+ const { product } = input;
434
+ return {
435
+ title: product ? `My Feature — ${product.name}` : 'My Feature',
436
+ description: 'A useful feature for your products.',
437
+ topics: ['My Feature'],
438
+ };
439
+ }
440
+
441
+ export async function getLLMContent(input: LLMContentInput): Promise<LLMContentResult> {
442
+ return { sections: [] };
443
+ }
444
+
445
+ export type { SEOInput, SEOResult, LLMContentInput, LLMContentResult };
446
+ ```
447
+
448
+ The manifest declares what's exported, and the server only calls what's available.
449
+
450
+ ---
451
+
452
+ ## Full Executor (With Configuration API)
453
+
454
+ For apps that want AI-driven setup and programmatic configuration, add a factory function with mutation methods:
455
+
456
+ ```typescript
457
+ // src/executor/index.ts
458
+ import type { ExecutorContext, SEOInput, SEOResult, LLMContentInput, LLMContentResult } from './types';
459
+
460
+ export function createMyAppExecutor(ctx: ExecutorContext) {
461
+ const { collectionId, appId, SL } = ctx;
462
+
463
+ async function getConfig() {
464
+ try {
465
+ return await SL.appConfiguration.getConfig({ collectionId, appId, admin: true });
466
+ } catch {
467
+ return DEFAULT_CONFIG;
468
+ }
469
+ }
470
+
471
+ async function saveConfig(config: MyConfig) {
472
+ // Validate with Zod or similar, then save
473
+ await SL.appConfiguration.setConfig({ collectionId, appId, config, admin: true });
474
+ }
475
+
476
+ return {
477
+ getConfig,
478
+ saveConfig,
479
+
480
+ /** Update the welcome message shown to first-time visitors */
481
+ async setWelcomeMessage(message: string) {
482
+ const config = await getConfig();
483
+ config.welcomeMessage = message;
484
+ await saveConfig(config);
485
+ return config;
486
+ },
487
+
488
+ async getSEO(input: SEOInput): Promise<SEOResult> { /* ... */ },
489
+ async getLLMContent(input: LLMContentInput): Promise<LLMContentResult> { /* ... */ },
490
+
491
+ /**
492
+ * Returns a human-readable summary of current config.
493
+ * Recommended for any app with configuration — enables conversational AI setup flows.
494
+ */
495
+ async describeCurrentConfig(): Promise<string> {
496
+ const config = await getConfig();
497
+ return `Welcome message: "${config.welcomeMessage}"`;
498
+ },
499
+ };
500
+ }
501
+ ```
502
+
503
+ The `describeCurrentConfig()` method is strongly recommended — it gives AI orchestrators situational awareness of the app's current state, enabling natural-language setup flows ("What is the app currently configured to show?").
504
+
505
+ ---
506
+
507
+ ## TypeScript Types
508
+
509
+ ```typescript
510
+ import type {
511
+ ExecutorContext, // { collectionId, appId, SL }
512
+ SEOInput, // getSEO() argument
513
+ SEOResult, // getSEO() return value
514
+ LLMContentInput, // getLLMContent() argument
515
+ LLMContentResult, // getLLMContent() return value
516
+ LLMContentSection, // { heading, content, order? }
517
+ AppManifestExecutor, // executor block in app.manifest.json
518
+ } from '@proveanything/smartlinks';
519
+ ```
520
+
521
+ All types live in `src/types/appManifest.ts`.
522
+
523
+ ---
524
+
525
+ ## Design Principles
526
+
527
+ | Principle | Detail |
528
+ |-----------|--------|
529
+ | **Pure transformations** | Mutation functions are `(Config) → Config` with no side effects |
530
+ | **Pre-fetched entities** | `getSEO()` and `getLLMContent()` receive data — they don't fetch it themselves |
531
+ | **200ms SEO budget** | The server enforces a hard timeout. Keep `getSEO()` synchronous if possible |
532
+ | **500ms LLM budget** | `getLLMContent()` may fetch app config, but should remain fast |
533
+ | **`admin: true` encapsulated** | Write operations always include the flag — callers never need to remember |
534
+ | **Validate before save** | Use Zod or similar to reject invalid configs before writing |
535
+ | **Idempotent mutations** | Repeated calls with the same input produce the same result |
536
+ | **Externalise only the SDK** | Bundle everything else — keep the dependency contract minimal |
537
+
538
+ ---
539
+
540
+ ## Reference: Hub Executor
541
+
542
+ The Product Hub app provides the most comprehensive executor implementation as a reference. Its executor includes:
543
+
544
+ - **Theme & branding** — `applyBranding()`, `setFonts()`, `setColors()` with hex→HSL conversion
545
+ - **Pages & content** — `addPage()` (idempotent by slug), `removePage()`, `addBlockToPage()`, `generateHomepage()`
546
+ - **Navigation** — `addNavItem()`, `setNavItems()`, `syncNavToPages()`
547
+ - **Header & footer** — `setHeader()`, `setFooter()`, `setFooterFromCrawl()`
548
+ - **Compliance** — `setCompliance()` (cookie consent, analytics, legal)
549
+ - **SEO** — `getSEO()` with layered metadata (brand → page → product)
550
+ - **LLM content** — `getLLMContent()` producing overview, pages, product, and contact sections
551
+ - **Bulk setup** — `setupFromWebsite()` orchestrating all of the above from crawled data
552
+ - **Describe** — `describeCurrentConfig()` for AI situational awareness
553
+
554
+ See `public/ai-guide.md` in the Hub app for complete API documentation.