@voyant-travel/apps 0.6.0 → 0.6.2

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.
@@ -0,0 +1,195 @@
1
+ import { createRoute, z } from "@hono/zod-openapi";
2
+ import { activateInstallationBodySchema, appCredentialRevocationSchema, appInstallationAuditQuerySchema, appInstallationListQuerySchema, appListQuerySchema, appOAuthAuthorizeQuerySchema, appOAuthTokenSchema, appSessionTokenExchangeSchema, appSessionTokenIssueSchema, appWebhookReplaySchema, createCustomAppRegistrationSchema, installAppSchema, lifecycleActionBodySchema, releaseManifestFetchSchema, releaseManifestUploadSchema, } from "./contracts.js";
3
+ const appIdParamSchema = z.object({ appId: z.string().min(1) });
4
+ const installationIdParamSchema = z.object({ installationId: z.string().min(1) });
5
+ const errorSchema = z.object({ error: z.string() });
6
+ const dataEnvelopeSchema = z.object({ data: z.unknown() });
7
+ const listEnvelopeSchema = z.object({
8
+ data: z.array(z.unknown()),
9
+ total: z.number(),
10
+ limit: z.number(),
11
+ offset: z.number(),
12
+ });
13
+ const releaseCreateResponseSchema = z.object({
14
+ data: z.unknown(),
15
+ digest: z.string(),
16
+ created: z.boolean(),
17
+ });
18
+ const oauthRedirectResponseSchema = z.object({
19
+ data: z.object({ redirectUrl: z.string().url(), state: z.string() }),
20
+ });
21
+ const tokenResponseSchema = z.record(z.string(), z.unknown());
22
+ const credentialRevocationResponseSchema = z.object({
23
+ installationId: z.string(),
24
+ generation: z.number(),
25
+ });
26
+ const jsonContent = (schema, description) => ({
27
+ description,
28
+ content: { "application/json": { schema } },
29
+ });
30
+ const requiredJsonBody = (schema) => ({
31
+ required: true,
32
+ content: { "application/json": { schema } },
33
+ });
34
+ export const listAppsRoute = createRoute({
35
+ method: "get",
36
+ path: "/",
37
+ request: { query: appListQuerySchema },
38
+ responses: { 200: jsonContent(listEnvelopeSchema, "Paginated app registrations") },
39
+ });
40
+ export const createCustomAppRoute = createRoute({
41
+ method: "post",
42
+ path: "/",
43
+ request: { body: requiredJsonBody(createCustomAppRegistrationSchema) },
44
+ responses: {
45
+ 201: jsonContent(dataEnvelopeSchema, "Created custom app registration"),
46
+ 409: jsonContent(errorSchema, "Duplicate app registration"),
47
+ },
48
+ });
49
+ export const authorizeAppOAuthRoute = createRoute({
50
+ method: "post",
51
+ path: "/oauth/authorize",
52
+ request: { body: requiredJsonBody(appOAuthAuthorizeQuerySchema) },
53
+ responses: {
54
+ 200: jsonContent(oauthRedirectResponseSchema, "OAuth authorization redirect target"),
55
+ 501: jsonContent(errorSchema, "App OAuth is not configured"),
56
+ },
57
+ });
58
+ export const issueAppOAuthTokenRoute = createRoute({
59
+ method: "post",
60
+ path: "/oauth/token",
61
+ request: { body: requiredJsonBody(appOAuthTokenSchema) },
62
+ responses: {
63
+ 200: jsonContent(tokenResponseSchema, "OAuth token response"),
64
+ 501: jsonContent(errorSchema, "App OAuth is not configured"),
65
+ },
66
+ });
67
+ export const revokeAppInstallationCredentialsRoute = createRoute({
68
+ method: "post",
69
+ path: "/oauth/revoke-installation",
70
+ request: { body: requiredJsonBody(appCredentialRevocationSchema) },
71
+ responses: {
72
+ 200: jsonContent(credentialRevocationResponseSchema, "Revoked installation credential generation"),
73
+ 501: jsonContent(errorSchema, "App OAuth is not configured"),
74
+ },
75
+ });
76
+ export const issueAppSessionTokenRoute = createRoute({
77
+ method: "post",
78
+ path: "/installations/{installationId}/session-token",
79
+ request: {
80
+ params: installationIdParamSchema,
81
+ body: requiredJsonBody(appSessionTokenIssueSchema),
82
+ },
83
+ responses: {
84
+ 201: jsonContent(dataEnvelopeSchema, "Issued app session token"),
85
+ 501: jsonContent(errorSchema, "App session tokens are not configured"),
86
+ },
87
+ });
88
+ export const exchangeAppSessionTokenRoute = createRoute({
89
+ method: "post",
90
+ path: "/oauth/session-token/exchange",
91
+ request: { body: requiredJsonBody(appSessionTokenExchangeSchema) },
92
+ responses: {
93
+ 200: jsonContent(tokenResponseSchema, "Online app token response"),
94
+ 501: jsonContent(errorSchema, "App session tokens are not configured"),
95
+ },
96
+ });
97
+ export const installAppRoute = createRoute({
98
+ method: "post",
99
+ path: "/install",
100
+ request: { body: requiredJsonBody(installAppSchema) },
101
+ responses: { 201: jsonContent(dataEnvelopeSchema, "Installed app") },
102
+ });
103
+ export const listAppInstallationsRoute = createRoute({
104
+ method: "get",
105
+ path: "/installations",
106
+ request: { query: appInstallationListQuerySchema },
107
+ responses: { 200: jsonContent(listEnvelopeSchema, "Paginated app installations") },
108
+ });
109
+ export const getAppInstallationRoute = createRoute({
110
+ method: "get",
111
+ path: "/installations/{installationId}",
112
+ request: { params: installationIdParamSchema },
113
+ responses: {
114
+ 200: jsonContent(dataEnvelopeSchema, "App installation detail"),
115
+ 404: jsonContent(errorSchema, "App installation not found"),
116
+ },
117
+ });
118
+ export const listAppInstallationAuditRoute = createRoute({
119
+ method: "get",
120
+ path: "/installations/{installationId}/audit",
121
+ request: { params: installationIdParamSchema, query: appInstallationAuditQuerySchema },
122
+ responses: { 200: jsonContent(dataEnvelopeSchema, "App installation audit events") },
123
+ });
124
+ export const pauseAppInstallationRoute = createRoute({
125
+ method: "post",
126
+ path: "/installations/{installationId}/pause",
127
+ request: { params: installationIdParamSchema, body: requiredJsonBody(lifecycleActionBodySchema) },
128
+ responses: { 200: jsonContent(dataEnvelopeSchema, "Paused app installation") },
129
+ });
130
+ export const resumeAppInstallationRoute = createRoute({
131
+ method: "post",
132
+ path: "/installations/{installationId}/resume",
133
+ request: { params: installationIdParamSchema, body: requiredJsonBody(lifecycleActionBodySchema) },
134
+ responses: { 200: jsonContent(dataEnvelopeSchema, "Resumed app installation") },
135
+ });
136
+ export const uninstallAppInstallationRoute = createRoute({
137
+ method: "post",
138
+ path: "/installations/{installationId}/uninstall",
139
+ request: { params: installationIdParamSchema, body: requiredJsonBody(lifecycleActionBodySchema) },
140
+ responses: { 200: jsonContent(dataEnvelopeSchema, "Uninstalled app installation") },
141
+ });
142
+ export const activateAppInstallationRoute = createRoute({
143
+ method: "post",
144
+ path: "/installations/{installationId}/activate",
145
+ request: {
146
+ params: installationIdParamSchema,
147
+ body: requiredJsonBody(activateInstallationBodySchema),
148
+ },
149
+ responses: { 200: jsonContent(dataEnvelopeSchema, "Activated app installation release") },
150
+ });
151
+ export const previewAppInstallationPurgeRoute = createRoute({
152
+ method: "post",
153
+ path: "/installations/{installationId}/purge-preview",
154
+ request: { params: installationIdParamSchema, body: requiredJsonBody(lifecycleActionBodySchema) },
155
+ responses: { 200: jsonContent(dataEnvelopeSchema, "App installation purge preview") },
156
+ });
157
+ export const getAppRoute = createRoute({
158
+ method: "get",
159
+ path: "/{appId}",
160
+ request: { params: appIdParamSchema },
161
+ responses: {
162
+ 200: jsonContent(dataEnvelopeSchema, "App registration"),
163
+ 404: jsonContent(errorSchema, "App not found"),
164
+ },
165
+ });
166
+ export const listAppReleasesRoute = createRoute({
167
+ method: "get",
168
+ path: "/{appId}/releases",
169
+ request: { params: appIdParamSchema },
170
+ responses: { 200: jsonContent(dataEnvelopeSchema, "App releases") },
171
+ });
172
+ export const createAppReleaseRoute = createRoute({
173
+ method: "post",
174
+ path: "/{appId}/releases",
175
+ request: { params: appIdParamSchema, body: requiredJsonBody(releaseManifestUploadSchema) },
176
+ responses: { 201: jsonContent(releaseCreateResponseSchema, "Created app release") },
177
+ });
178
+ export const fetchAppReleaseRoute = createRoute({
179
+ method: "post",
180
+ path: "/{appId}/releases/fetch",
181
+ request: { params: appIdParamSchema, body: requiredJsonBody(releaseManifestFetchSchema) },
182
+ responses: { 201: jsonContent(releaseCreateResponseSchema, "Fetched app release") },
183
+ });
184
+ export const listAppWebhooksRoute = createRoute({
185
+ method: "get",
186
+ path: "/installations/{installationId}/webhooks",
187
+ request: { params: installationIdParamSchema },
188
+ responses: { 200: jsonContent(dataEnvelopeSchema, "App webhook delivery health") },
189
+ });
190
+ export const replayAppWebhookRoute = createRoute({
191
+ method: "post",
192
+ path: "/installations/{installationId}/webhooks/replay",
193
+ request: { params: installationIdParamSchema, body: requiredJsonBody(appWebhookReplaySchema) },
194
+ responses: { 202: jsonContent(dataEnvelopeSchema, "Replayed app webhook delivery") },
195
+ });
package/dist/routes.d.ts CHANGED
@@ -1,8 +1,8 @@
1
+ import { OpenAPIHono } from "@hono/zod-openapi";
1
2
  import type { EventBus } from "@voyant-travel/core/events";
2
3
  import type { createCustomFieldsService } from "@voyant-travel/custom-fields";
3
4
  import type { AccessCatalog } from "@voyant-travel/types/api-keys";
4
5
  import type { PostgresJsDatabase } from "drizzle-orm/postgres-js";
5
- import { Hono } from "hono";
6
6
  import { type AppsServiceOptions } from "./service.js";
7
7
  type CustomFieldsService = ReturnType<typeof createCustomFieldsService>;
8
8
  type Env = {
@@ -38,5 +38,5 @@ export interface AppsAdminRouteOptions extends AppsServiceOptions {
38
38
  eventBus?: EventBus;
39
39
  customFields?: CustomFieldsService;
40
40
  }
41
- export declare function createAppsAdminRoutes(options?: AppsAdminRouteOptions): Hono<Env, import("hono/types").BlankSchema, "/">;
41
+ export declare function createAppsAdminRoutes(options?: AppsAdminRouteOptions): OpenAPIHono<Env, {}, "/">;
42
42
  export {};
package/dist/routes.js CHANGED
@@ -1,17 +1,18 @@
1
- import { parseJsonBody, parseQuery, RequestValidationError, requireUserId, } from "@voyant-travel/hono";
2
- import { Hono } from "hono";
1
+ import { OpenAPIHono } from "@hono/zod-openapi";
2
+ import { openApiValidationHook, parseJsonBody, parseQuery, RequestValidationError, requireUserId, } from "@voyant-travel/hono";
3
3
  import { z } from "zod";
4
4
  import { activateInstallationBodySchema, appCredentialRevocationSchema, appInstallationAuditQuerySchema, appInstallationListQuerySchema, appListQuerySchema, appOAuthAuthorizeQuerySchema, appOAuthTokenSchema, appSessionTokenExchangeSchema, appSessionTokenIssueSchema, appWebhookReplaySchema, createCustomAppRegistrationSchema, installAppSchema, lifecycleActionBodySchema, releaseManifestFetchSchema, releaseManifestUploadSchema, } from "./contracts.js";
5
5
  import { listAppReleases, listInstallationAudit, listInstallationSummaries, loadInstallationDetail, } from "./installation-read-model.js";
6
6
  import { createAppInstallationService } from "./installation-service.js";
7
7
  import { createAppOAuthService } from "./oauth-service.js";
8
+ import { activateAppInstallationRoute, authorizeAppOAuthRoute, createAppReleaseRoute, createCustomAppRoute, exchangeAppSessionTokenRoute, fetchAppReleaseRoute, getAppInstallationRoute, getAppRoute, installAppRoute, issueAppOAuthTokenRoute, issueAppSessionTokenRoute, listAppInstallationAuditRoute, listAppInstallationsRoute, listAppReleasesRoute, listAppsRoute, listAppWebhooksRoute, pauseAppInstallationRoute, previewAppInstallationPurgeRoute, replayAppWebhookRoute, resumeAppInstallationRoute, revokeAppInstallationCredentialsRoute, uninstallAppInstallationRoute, } from "./routes-openapi.js";
8
9
  import { createAppsService } from "./service.js";
9
10
  import { createAppSessionTokenService } from "./session-token-service.js";
10
11
  import { listAppWebhookHealth, replayAppWebhookDelivery } from "./webhook-delivery.js";
11
12
  const appIdParamSchema = z.object({ appId: z.string().min(1) });
12
13
  const installationIdParamSchema = z.object({ installationId: z.string().min(1) });
13
14
  export function createAppsAdminRoutes(options = {}) {
14
- const routes = new Hono();
15
+ const routes = new OpenAPIHono({ defaultHook: openApiValidationHook });
15
16
  const service = createAppsService(options);
16
17
  const installations = createAppInstallationService({
17
18
  deploymentId: options.oauth?.deploymentId ?? options.deploymentId,
@@ -28,11 +29,11 @@ export function createAppsAdminRoutes(options = {}) {
28
29
  oauth,
29
30
  })
30
31
  : null;
31
- routes.get("/", async (c) => {
32
+ routes.openapi(listAppsRoute, async (c) => {
32
33
  const query = parseQuery(c, appListQuerySchema);
33
34
  return c.json(await service.list(c.get("db"), query), 200);
34
35
  });
35
- routes.post("/", async (c) => {
36
+ routes.openapi(createCustomAppRoute, async (c) => {
36
37
  const body = await parseJsonBody(c, createCustomAppRegistrationSchema);
37
38
  const app = await service.createCustomApp(c.get("db"), body);
38
39
  return c.json({ data: app }, 201);
@@ -40,7 +41,7 @@ export function createAppsAdminRoutes(options = {}) {
40
41
  // Consent approval mutates state (installation, grants, authorization code),
41
42
  // so it must never be reachable through read-scoped GET requests. The admin
42
43
  // consent UI submits the approval and performs the redirect itself.
43
- routes.post("/oauth/authorize", async (c) => {
44
+ routes.openapi(authorizeAppOAuthRoute, async (c) => {
44
45
  if (!oauth)
45
46
  return c.json({ error: "App OAuth is not configured" }, 501);
46
47
  const body = await parseJsonBody(c, appOAuthAuthorizeQuerySchema);
@@ -60,7 +61,7 @@ export function createAppsAdminRoutes(options = {}) {
60
61
  redirectUrl.searchParams.set("state", result.state);
61
62
  return c.json({ data: { redirectUrl: redirectUrl.toString(), state: result.state } }, 200);
62
63
  });
63
- routes.post("/oauth/token", async (c) => {
64
+ routes.openapi(issueAppOAuthTokenRoute, async (c) => {
64
65
  if (!oauth)
65
66
  return c.json({ error: "App OAuth is not configured" }, 501);
66
67
  const body = await parseJsonBody(c, appOAuthTokenSchema);
@@ -91,7 +92,7 @@ export function createAppsAdminRoutes(options = {}) {
91
92
  });
92
93
  return c.json(token, 200);
93
94
  });
94
- routes.post("/oauth/revoke-installation", async (c) => {
95
+ routes.openapi(revokeAppInstallationCredentialsRoute, async (c) => {
95
96
  if (!oauth)
96
97
  return c.json({ error: "App OAuth is not configured" }, 501);
97
98
  const body = await parseJsonBody(c, appCredentialRevocationSchema);
@@ -101,7 +102,7 @@ export function createAppsAdminRoutes(options = {}) {
101
102
  // Staff-authenticated: the admin host requests a short-lived session token for
102
103
  // the current viewer + entity/slot context. The viewer is taken from the
103
104
  // authenticated session, never from the frame.
104
- routes.post("/installations/:installationId/session-token", async (c) => {
105
+ routes.openapi(issueAppSessionTokenRoute, async (c) => {
105
106
  if (!sessionTokens)
106
107
  return c.json({ error: "App session tokens are not configured" }, 501);
107
108
  const { installationId } = parseInstallationParams(c.req.param());
@@ -117,7 +118,7 @@ export function createAppsAdminRoutes(options = {}) {
117
118
  });
118
119
  // App-backend-facing: exchange a presented session token for online actor
119
120
  // access. Client-authenticated; bounded by viewer ∩ app grants.
120
- routes.post("/oauth/session-token/exchange", async (c) => {
121
+ routes.openapi(exchangeAppSessionTokenRoute, async (c) => {
121
122
  if (!sessionTokens)
122
123
  return c.json({ error: "App session tokens are not configured" }, 501);
123
124
  const body = await parseJsonBody(c, appSessionTokenExchangeSchema);
@@ -130,7 +131,7 @@ export function createAppsAdminRoutes(options = {}) {
130
131
  });
131
132
  return c.json(token, 200);
132
133
  });
133
- routes.post("/install", async (c) => {
134
+ routes.openapi(installAppRoute, async (c) => {
134
135
  const body = await parseJsonBody(c, installAppSchema);
135
136
  // The deployment id is a runtime value (not known at graph-composition
136
137
  // time), so resolve it per request: explicit body → runtime env →
@@ -147,11 +148,11 @@ export function createAppsAdminRoutes(options = {}) {
147
148
  });
148
149
  return c.json({ data: result }, 201);
149
150
  });
150
- routes.get("/installations", async (c) => {
151
+ routes.openapi(listAppInstallationsRoute, async (c) => {
151
152
  const query = parseQuery(c, appInstallationListQuerySchema);
152
153
  return c.json(await listInstallationSummaries(c.get("db"), query), 200);
153
154
  });
154
- routes.get("/installations/:installationId", async (c) => {
155
+ routes.openapi(getAppInstallationRoute, async (c) => {
155
156
  const { installationId } = parseInstallationParams(c.req.param());
156
157
  const detail = await loadInstallationDetail(c.get("db"), installationId, {
157
158
  platformApiVersion: options.platformApiVersion,
@@ -160,19 +161,19 @@ export function createAppsAdminRoutes(options = {}) {
160
161
  ? c.json({ data: detail }, 200)
161
162
  : c.json({ error: "App installation not found" }, 404);
162
163
  });
163
- routes.get("/installations/:installationId/audit", async (c) => {
164
+ routes.openapi(listAppInstallationAuditRoute, async (c) => {
164
165
  const { installationId } = parseInstallationParams(c.req.param());
165
166
  const query = parseQuery(c, appInstallationAuditQuerySchema);
166
167
  const data = await listInstallationAudit(c.get("db"), installationId, query.limit);
167
168
  return c.json({ data }, 200);
168
169
  });
169
- routes.post("/installations/:installationId/pause", async (c) => {
170
+ routes.openapi(pauseAppInstallationRoute, async (c) => {
170
171
  const { installationId } = parseInstallationParams(c.req.param());
171
172
  const body = await parseJsonBody(c, lifecycleActionBodySchema);
172
173
  const result = await installations.pause(c.get("db"), { installationId, actorId: body.actorId });
173
174
  return c.json({ data: result }, 200);
174
175
  });
175
- routes.post("/installations/:installationId/resume", async (c) => {
176
+ routes.openapi(resumeAppInstallationRoute, async (c) => {
176
177
  const { installationId } = parseInstallationParams(c.req.param());
177
178
  const body = await parseJsonBody(c, lifecycleActionBodySchema);
178
179
  const result = await installations.resume(c.get("db"), {
@@ -181,7 +182,7 @@ export function createAppsAdminRoutes(options = {}) {
181
182
  });
182
183
  return c.json({ data: result }, 200);
183
184
  });
184
- routes.post("/installations/:installationId/uninstall", async (c) => {
185
+ routes.openapi(uninstallAppInstallationRoute, async (c) => {
185
186
  const { installationId } = parseInstallationParams(c.req.param());
186
187
  const body = await parseJsonBody(c, lifecycleActionBodySchema);
187
188
  const result = await installations.uninstall(c.get("db"), {
@@ -190,7 +191,7 @@ export function createAppsAdminRoutes(options = {}) {
190
191
  });
191
192
  return c.json({ data: result }, 200);
192
193
  });
193
- routes.post("/installations/:installationId/activate", async (c) => {
194
+ routes.openapi(activateAppInstallationRoute, async (c) => {
194
195
  const { installationId } = parseInstallationParams(c.req.param());
195
196
  const body = await parseJsonBody(c, activateInstallationBodySchema);
196
197
  const result = await installations.upgrade(c.get("db"), {
@@ -200,7 +201,7 @@ export function createAppsAdminRoutes(options = {}) {
200
201
  });
201
202
  return c.json({ data: result }, 200);
202
203
  });
203
- routes.post("/installations/:installationId/purge-preview", async (c) => {
204
+ routes.openapi(previewAppInstallationPurgeRoute, async (c) => {
204
205
  const { installationId } = parseInstallationParams(c.req.param());
205
206
  const body = await parseJsonBody(c, lifecycleActionBodySchema);
206
207
  const result = await installations.purgePreview(c.get("db"), {
@@ -209,33 +210,33 @@ export function createAppsAdminRoutes(options = {}) {
209
210
  });
210
211
  return c.json({ data: result }, 200);
211
212
  });
212
- routes.get("/:appId", async (c) => {
213
+ routes.openapi(getAppRoute, async (c) => {
213
214
  const { appId } = parseParams(c.req.param());
214
215
  const app = await service.get(c.get("db"), appId);
215
216
  return app ? c.json({ data: app }, 200) : c.json({ error: "App not found" }, 404);
216
217
  });
217
- routes.get("/:appId/releases", async (c) => {
218
+ routes.openapi(listAppReleasesRoute, async (c) => {
218
219
  const { appId } = parseParams(c.req.param());
219
220
  const data = await listAppReleases(c.get("db"), appId);
220
221
  return c.json({ data }, 200);
221
222
  });
222
- routes.post("/:appId/releases", async (c) => {
223
+ routes.openapi(createAppReleaseRoute, async (c) => {
223
224
  const { appId } = parseParams(c.req.param());
224
225
  const body = await parseJsonBody(c, releaseManifestUploadSchema);
225
226
  const result = await service.releaseFromUpload(c.get("db"), appId, body);
226
227
  return c.json({ data: result.release, digest: result.digest, created: result.created }, 201);
227
228
  });
228
- routes.post("/:appId/releases/fetch", async (c) => {
229
+ routes.openapi(fetchAppReleaseRoute, async (c) => {
229
230
  const { appId } = parseParams(c.req.param());
230
231
  const body = await parseJsonBody(c, releaseManifestFetchSchema);
231
232
  const result = await service.releaseFromFetch(c.get("db"), appId, body);
232
233
  return c.json({ data: result.release, digest: result.digest, created: result.created }, 201);
233
234
  });
234
- routes.get("/installations/:installationId/webhooks", async (c) => {
235
+ routes.openapi(listAppWebhooksRoute, async (c) => {
235
236
  const { installationId } = parseInstallationParams(c.req.param());
236
237
  return c.json(await listAppWebhookHealth(c.get("db"), installationId), 200);
237
238
  });
238
- routes.post("/installations/:installationId/webhooks/replay", async (c) => {
239
+ routes.openapi(replayAppWebhookRoute, async (c) => {
239
240
  parseInstallationParams(c.req.param());
240
241
  const body = await parseJsonBody(c, appWebhookReplaySchema);
241
242
  const delivery = await replayAppWebhookDelivery(c.get("db"), {
@@ -0,0 +1 @@
1
+ export declare function createAppsRuntimePortContribution(): Readonly<Record<string, unknown>>;
@@ -0,0 +1,3 @@
1
+ export function createAppsRuntimePortContribution() {
2
+ return {};
3
+ }
package/dist/voyant.js CHANGED
@@ -33,6 +33,7 @@ export const appsVoyantModule = defineModule({
33
33
  id: "@voyant-travel/apps#api.admin",
34
34
  surface: "admin",
35
35
  mount: "apps",
36
+ openapi: { document: "apps" },
36
37
  resource: "apps",
37
38
  transactional: true,
38
39
  runtime: {
@@ -111,7 +112,18 @@ export const appsVoyantModule = defineModule({
111
112
  label: "Own custom-field definitions",
112
113
  description: "Read and write custom-field definitions owned by the app.",
113
114
  remoteSafe: true,
114
- actions: ["read", "write"],
115
+ actions: [
116
+ {
117
+ action: "read",
118
+ label: "Read custom-field definitions",
119
+ description: "Read definitions owned by the app.",
120
+ },
121
+ {
122
+ action: "write",
123
+ label: "Write custom-field definitions",
124
+ description: "Create and update definitions owned by the app.",
125
+ },
126
+ ],
115
127
  wildcard: "explicit-resource",
116
128
  },
117
129
  {
@@ -120,7 +132,38 @@ export const appsVoyantModule = defineModule({
120
132
  label: "Own custom-field values",
121
133
  description: "Read and write app-owned custom-field values by target.",
122
134
  remoteSafe: true,
123
- actions: ["read", "write", "booking", "person", "organization", "invoice"],
135
+ actions: [
136
+ {
137
+ action: "read",
138
+ label: "Read custom-field values",
139
+ description: "Read custom-field values owned by the app.",
140
+ },
141
+ {
142
+ action: "write",
143
+ label: "Write custom-field values",
144
+ description: "Write custom-field values owned by the app.",
145
+ },
146
+ {
147
+ action: "booking",
148
+ label: "Booking custom fields",
149
+ description: "Access app-owned custom-field values on bookings.",
150
+ },
151
+ {
152
+ action: "person",
153
+ label: "Person custom fields",
154
+ description: "Access app-owned custom-field values on people.",
155
+ },
156
+ {
157
+ action: "organization",
158
+ label: "Organization custom fields",
159
+ description: "Access app-owned custom-field values on organizations.",
160
+ },
161
+ {
162
+ action: "invoice",
163
+ label: "Invoice custom fields",
164
+ description: "Access app-owned custom-field values on invoices.",
165
+ },
166
+ ],
124
167
  wildcard: "explicit-resource",
125
168
  },
126
169
  {
@@ -133,7 +176,11 @@ export const appsVoyantModule = defineModule({
133
176
  description: "Read webhook health and request replay.",
134
177
  remoteSafe: true,
135
178
  actions: [
136
- "read",
179
+ {
180
+ action: "read",
181
+ label: "Read app webhooks",
182
+ description: "Read app webhook subscription and delivery health.",
183
+ },
137
184
  { action: "replay", label: "Replay webhooks", description: "Replay deliveries." },
138
185
  ],
139
186
  },
@@ -143,7 +190,13 @@ export const appsVoyantModule = defineModule({
143
190
  label: "App audit history",
144
191
  description: "Read audit history owned by the app.",
145
192
  remoteSafe: true,
146
- actions: ["read"],
193
+ actions: [
194
+ {
195
+ action: "read",
196
+ label: "Read app audit history",
197
+ description: "Read audit events owned by the app.",
198
+ },
199
+ ],
147
200
  },
148
201
  {
149
202
  id: "@voyant-travel/apps#access.online-token",
@@ -151,7 +204,14 @@ export const appsVoyantModule = defineModule({
151
204
  label: "Online token exchange",
152
205
  description: "Exchange admin session context for online app access.",
153
206
  remoteSafe: true,
154
- actions: [{ action: "exchange", wildcard: "explicit" }],
207
+ actions: [
208
+ {
209
+ action: "exchange",
210
+ label: "Exchange online token",
211
+ description: "Exchange admin session context for online app access.",
212
+ wildcard: "explicit",
213
+ },
214
+ ],
155
215
  },
156
216
  {
157
217
  id: "@voyant-travel/apps#access.finance-documents",
@@ -159,7 +219,13 @@ export const appsVoyantModule = defineModule({
159
219
  label: "Finance documents",
160
220
  description: "Read finance documents visible to remote accounting apps.",
161
221
  remoteSafe: true,
162
- actions: ["read"],
222
+ actions: [
223
+ {
224
+ action: "read",
225
+ label: "Read finance documents",
226
+ description: "Read finance documents visible to the app.",
227
+ },
228
+ ],
163
229
  wildcard: "explicit-resource",
164
230
  },
165
231
  {
@@ -169,9 +235,27 @@ export const appsVoyantModule = defineModule({
169
235
  description: "Request approved finance document actions.",
170
236
  remoteSafe: true,
171
237
  actions: [
172
- { action: "issue", sensitive: true, wildcard: "explicit" },
173
- { action: "retry", sensitive: true, wildcard: "explicit" },
174
- { action: "reconcile", sensitive: true, wildcard: "explicit" },
238
+ {
239
+ action: "issue",
240
+ label: "Issue finance documents",
241
+ description: "Request approved finance document issuance.",
242
+ sensitive: true,
243
+ wildcard: "explicit",
244
+ },
245
+ {
246
+ action: "retry",
247
+ label: "Retry finance actions",
248
+ description: "Request approved finance action retry.",
249
+ sensitive: true,
250
+ wildcard: "explicit",
251
+ },
252
+ {
253
+ action: "reconcile",
254
+ label: "Reconcile finance documents",
255
+ description: "Request approved finance document reconciliation.",
256
+ sensitive: true,
257
+ wildcard: "explicit",
258
+ },
175
259
  ],
176
260
  wildcard: "explicit-resource",
177
261
  },
@@ -0,0 +1,72 @@
1
+ {
2
+ "openapi": "3.1.0",
3
+ "info": {
4
+ "title": "Voyant Apps API",
5
+ "version": "1.0.0",
6
+ "description": "Operator app registration, release, installation, consent, token, and webhook governance routes."
7
+ },
8
+ "paths": {
9
+ "/v1/admin/apps": {
10
+ "get": { "summary": "List app registrations" },
11
+ "post": { "summary": "Create a custom app registration" }
12
+ },
13
+ "/v1/admin/apps/oauth/authorize": {
14
+ "post": { "summary": "Approve app OAuth consent" }
15
+ },
16
+ "/v1/admin/apps/oauth/token": {
17
+ "post": { "summary": "Issue an app OAuth token" }
18
+ },
19
+ "/v1/admin/apps/oauth/revoke-installation": {
20
+ "post": { "summary": "Revoke installation credentials" }
21
+ },
22
+ "/v1/admin/apps/oauth/session-token/exchange": {
23
+ "post": { "summary": "Exchange an app session token" }
24
+ },
25
+ "/v1/admin/apps/install": {
26
+ "post": { "summary": "Install an app release" }
27
+ },
28
+ "/v1/admin/apps/installations": {
29
+ "get": { "summary": "List app installations" }
30
+ },
31
+ "/v1/admin/apps/installations/{installationId}": {
32
+ "get": { "summary": "Get app installation detail" }
33
+ },
34
+ "/v1/admin/apps/installations/{installationId}/audit": {
35
+ "get": { "summary": "List app installation audit events" }
36
+ },
37
+ "/v1/admin/apps/installations/{installationId}/pause": {
38
+ "post": { "summary": "Pause an app installation" }
39
+ },
40
+ "/v1/admin/apps/installations/{installationId}/resume": {
41
+ "post": { "summary": "Resume an app installation" }
42
+ },
43
+ "/v1/admin/apps/installations/{installationId}/uninstall": {
44
+ "post": { "summary": "Uninstall an app installation" }
45
+ },
46
+ "/v1/admin/apps/installations/{installationId}/activate": {
47
+ "post": { "summary": "Activate an app installation release" }
48
+ },
49
+ "/v1/admin/apps/installations/{installationId}/purge-preview": {
50
+ "post": { "summary": "Preview app installation purge impact" }
51
+ },
52
+ "/v1/admin/apps/installations/{installationId}/session-token": {
53
+ "post": { "summary": "Issue an app extension session token" }
54
+ },
55
+ "/v1/admin/apps/installations/{installationId}/webhooks": {
56
+ "get": { "summary": "List app webhook delivery health" }
57
+ },
58
+ "/v1/admin/apps/installations/{installationId}/webhooks/replay": {
59
+ "post": { "summary": "Replay an app webhook delivery" }
60
+ },
61
+ "/v1/admin/apps/{appId}": {
62
+ "get": { "summary": "Get an app registration" }
63
+ },
64
+ "/v1/admin/apps/{appId}/releases": {
65
+ "get": { "summary": "List app releases" },
66
+ "post": { "summary": "Create an app release from an uploaded manifest" }
67
+ },
68
+ "/v1/admin/apps/{appId}/releases/fetch": {
69
+ "post": { "summary": "Create an app release from a fetched manifest" }
70
+ }
71
+ }
72
+ }