@ainyc/canonry 1.20.0 → 1.20.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.
@@ -378,6 +378,9 @@ function authInvalid() {
378
378
  function runInProgress(projectName) {
379
379
  return new AppError("RUN_IN_PROGRESS", `A run is already in progress for '${projectName}'`, 409);
380
380
  }
381
+ function runNotCancellable(runId, status) {
382
+ return new AppError("RUN_NOT_CANCELLABLE", `Run '${runId}' is already in terminal state '${status}' and cannot be cancelled`, 409);
383
+ }
381
384
  function unsupportedKind(kind) {
382
385
  return new AppError("UNSUPPORTED_KIND", `Kind '${kind}' is not supported in this version`, 400);
383
386
  }
@@ -555,7 +558,7 @@ function effectiveDomains(project) {
555
558
 
556
559
  // ../contracts/src/run.ts
557
560
  import { z as z7 } from "zod";
558
- var runStatusSchema = z7.enum(["queued", "running", "completed", "partial", "failed"]);
561
+ var runStatusSchema = z7.enum(["queued", "running", "completed", "partial", "failed", "cancelled"]);
559
562
  var runKindSchema = z7.enum(["answer-visibility", "site-audit", "gsc-sync", "inspect-sitemap"]);
560
563
  var runTriggerSchema = z7.enum(["manual", "scheduled", "config-apply"]);
561
564
  var citationStateSchema = z7.enum(["cited", "not-cited"]);
@@ -1997,6 +2000,29 @@ async function runRoutes(app, opts) {
1997
2000
  }
1998
2001
  return reply.status(207).send(results);
1999
2002
  });
2003
+ app.post("/runs/:id/cancel", async (request, reply) => {
2004
+ const run = app.db.select().from(runs).where(eq7(runs.id, request.params.id)).get();
2005
+ if (!run) {
2006
+ const err = notFound("Run", request.params.id);
2007
+ return reply.status(err.statusCode).send(err.toJSON());
2008
+ }
2009
+ const terminalStatuses = /* @__PURE__ */ new Set(["completed", "partial", "failed", "cancelled"]);
2010
+ if (terminalStatuses.has(run.status)) {
2011
+ const err = runNotCancellable(run.id, run.status);
2012
+ return reply.status(err.statusCode).send(err.toJSON());
2013
+ }
2014
+ const now = (/* @__PURE__ */ new Date()).toISOString();
2015
+ app.db.update(runs).set({ status: "cancelled", finishedAt: now, error: "Cancelled by user" }).where(eq7(runs.id, run.id)).run();
2016
+ writeAuditLog(app.db, {
2017
+ projectId: run.projectId,
2018
+ actor: "api",
2019
+ action: "run.cancelled",
2020
+ entityType: "run",
2021
+ entityId: run.id
2022
+ });
2023
+ const updated = app.db.select().from(runs).where(eq7(runs.id, run.id)).get();
2024
+ return reply.send(formatRun(updated));
2025
+ });
2000
2026
  app.get("/runs/:id", async (request, reply) => {
2001
2027
  const run = app.db.select().from(runs).where(eq7(runs.id, request.params.id)).get();
2002
2028
  if (!run) {
@@ -3066,6 +3092,18 @@ var booleanSchema = { type: "boolean" };
3066
3092
  var integerSchema = { type: "integer" };
3067
3093
  var objectSchema = { type: "object", additionalProperties: true };
3068
3094
  var stringArraySchema = { type: "array", items: stringSchema };
3095
+ var googleConnectionTypeSchema2 = { type: "string", enum: ["gsc", "ga4"] };
3096
+ var locationSchema = {
3097
+ type: "object",
3098
+ required: ["label", "city", "region", "country"],
3099
+ properties: {
3100
+ label: stringSchema,
3101
+ city: stringSchema,
3102
+ region: stringSchema,
3103
+ country: stringSchema,
3104
+ timezone: stringSchema
3105
+ }
3106
+ };
3069
3107
  var nameParameter = {
3070
3108
  name: "name",
3071
3109
  in: "path",
@@ -3094,6 +3132,58 @@ var providerNameParameter = {
3094
3132
  description: "Provider name.",
3095
3133
  schema: { type: "string", enum: ["gemini", "openai", "claude", "local"] }
3096
3134
  };
3135
+ var locationLabelParameter = {
3136
+ name: "label",
3137
+ in: "path",
3138
+ required: true,
3139
+ description: "Location label.",
3140
+ schema: stringSchema
3141
+ };
3142
+ var googleTypeParameter = {
3143
+ name: "type",
3144
+ in: "path",
3145
+ required: true,
3146
+ description: "Google connection type.",
3147
+ schema: googleConnectionTypeSchema2
3148
+ };
3149
+ var projectRunIdParameter = {
3150
+ name: "runId",
3151
+ in: "path",
3152
+ required: true,
3153
+ description: "Run ID for a project run.",
3154
+ schema: stringSchema
3155
+ };
3156
+ var snapshotIdParameter = {
3157
+ name: "snapshotId",
3158
+ in: "path",
3159
+ required: true,
3160
+ description: "Snapshot ID.",
3161
+ schema: stringSchema
3162
+ };
3163
+ var limitQueryParameter = {
3164
+ name: "limit",
3165
+ in: "query",
3166
+ description: "Maximum number of records to return.",
3167
+ schema: integerSchema
3168
+ };
3169
+ var offsetQueryParameter = {
3170
+ name: "offset",
3171
+ in: "query",
3172
+ description: "Number of records to skip.",
3173
+ schema: integerSchema
3174
+ };
3175
+ var locationQueryParameter = {
3176
+ name: "location",
3177
+ in: "query",
3178
+ description: "Filter by location label. Use an empty value to request locationless results.",
3179
+ schema: stringSchema
3180
+ };
3181
+ var analyticsWindowParameter = {
3182
+ name: "window",
3183
+ in: "query",
3184
+ description: "Time window for analytics queries.",
3185
+ schema: { type: "string", enum: ["7d", "30d", "90d", "all"] }
3186
+ };
3097
3187
  var routeCatalog = [
3098
3188
  {
3099
3189
  method: "get",
@@ -3122,11 +3212,14 @@ var routeCatalog = [
3122
3212
  properties: {
3123
3213
  displayName: stringSchema,
3124
3214
  canonicalDomain: stringSchema,
3215
+ ownedDomains: stringArraySchema,
3125
3216
  country: stringSchema,
3126
3217
  language: stringSchema,
3127
3218
  tags: stringArraySchema,
3128
3219
  labels: objectSchema,
3129
3220
  providers: stringArraySchema,
3221
+ locations: { type: "array", items: locationSchema },
3222
+ defaultLocation: stringSchema,
3130
3223
  configSource: stringSchema
3131
3224
  }
3132
3225
  }
@@ -3169,6 +3262,75 @@ var routeCatalog = [
3169
3262
  404: { description: "Project not found." }
3170
3263
  }
3171
3264
  },
3265
+ {
3266
+ method: "post",
3267
+ path: "/api/v1/projects/{name}/locations",
3268
+ summary: "Add a project location",
3269
+ tags: ["projects"],
3270
+ parameters: [nameParameter],
3271
+ requestBody: {
3272
+ required: true,
3273
+ content: {
3274
+ "application/json": {
3275
+ schema: locationSchema
3276
+ }
3277
+ }
3278
+ },
3279
+ responses: {
3280
+ 201: { description: "Location created." },
3281
+ 400: { description: "Invalid location." },
3282
+ 404: { description: "Project not found." }
3283
+ }
3284
+ },
3285
+ {
3286
+ method: "get",
3287
+ path: "/api/v1/projects/{name}/locations",
3288
+ summary: "List project locations",
3289
+ tags: ["projects"],
3290
+ parameters: [nameParameter],
3291
+ responses: {
3292
+ 200: { description: "Locations returned." },
3293
+ 404: { description: "Project not found." }
3294
+ }
3295
+ },
3296
+ {
3297
+ method: "delete",
3298
+ path: "/api/v1/projects/{name}/locations/{label}",
3299
+ summary: "Remove a project location",
3300
+ tags: ["projects"],
3301
+ parameters: [nameParameter, locationLabelParameter],
3302
+ responses: {
3303
+ 204: { description: "Location removed." },
3304
+ 400: { description: "Invalid location." },
3305
+ 404: { description: "Project or location not found." }
3306
+ }
3307
+ },
3308
+ {
3309
+ method: "put",
3310
+ path: "/api/v1/projects/{name}/locations/default",
3311
+ summary: "Set the default project location",
3312
+ tags: ["projects"],
3313
+ parameters: [nameParameter],
3314
+ requestBody: {
3315
+ required: true,
3316
+ content: {
3317
+ "application/json": {
3318
+ schema: {
3319
+ type: "object",
3320
+ required: ["label"],
3321
+ properties: {
3322
+ label: stringSchema
3323
+ }
3324
+ }
3325
+ }
3326
+ }
3327
+ },
3328
+ responses: {
3329
+ 200: { description: "Default location updated." },
3330
+ 400: { description: "Invalid location." },
3331
+ 404: { description: "Project not found." }
3332
+ }
3333
+ },
3172
3334
  {
3173
3335
  method: "get",
3174
3336
  path: "/api/v1/projects/{name}/export",
@@ -3214,6 +3376,31 @@ var routeCatalog = [
3214
3376
  200: { description: "Keywords replaced." }
3215
3377
  }
3216
3378
  },
3379
+ {
3380
+ method: "delete",
3381
+ path: "/api/v1/projects/{name}/keywords",
3382
+ summary: "Delete specific keywords",
3383
+ tags: ["keywords"],
3384
+ parameters: [nameParameter],
3385
+ requestBody: {
3386
+ required: true,
3387
+ content: {
3388
+ "application/json": {
3389
+ schema: {
3390
+ type: "object",
3391
+ required: ["keywords"],
3392
+ properties: {
3393
+ keywords: stringArraySchema
3394
+ }
3395
+ }
3396
+ }
3397
+ }
3398
+ },
3399
+ responses: {
3400
+ 200: { description: "Remaining keywords returned." },
3401
+ 400: { description: "Invalid keyword delete request." }
3402
+ }
3403
+ },
3217
3404
  {
3218
3405
  method: "post",
3219
3406
  path: "/api/v1/projects/{name}/keywords",
@@ -3312,7 +3499,10 @@ var routeCatalog = [
3312
3499
  properties: {
3313
3500
  kind: stringSchema,
3314
3501
  trigger: stringSchema,
3315
- providers: stringArraySchema
3502
+ providers: stringArraySchema,
3503
+ location: stringSchema,
3504
+ allLocations: booleanSchema,
3505
+ noLocation: booleanSchema
3316
3506
  }
3317
3507
  }
3318
3508
  }
@@ -3375,6 +3565,18 @@ var routeCatalog = [
3375
3565
  404: { description: "Run not found." }
3376
3566
  }
3377
3567
  },
3568
+ {
3569
+ method: "post",
3570
+ path: "/api/v1/runs/{id}/cancel",
3571
+ summary: "Cancel a queued or running run",
3572
+ tags: ["runs"],
3573
+ parameters: [runIdParameter],
3574
+ responses: {
3575
+ 200: { description: "Run cancelled." },
3576
+ 404: { description: "Run not found." },
3577
+ 409: { description: "Run is not cancellable." }
3578
+ }
3579
+ },
3378
3580
  {
3379
3581
  method: "post",
3380
3582
  path: "/api/v1/apply",
@@ -3420,18 +3622,9 @@ var routeCatalog = [
3420
3622
  tags: ["history"],
3421
3623
  parameters: [
3422
3624
  nameParameter,
3423
- {
3424
- name: "limit",
3425
- in: "query",
3426
- description: "Maximum number of snapshots to return.",
3427
- schema: integerSchema
3428
- },
3429
- {
3430
- name: "offset",
3431
- in: "query",
3432
- description: "Number of snapshots to skip.",
3433
- schema: integerSchema
3434
- }
3625
+ limitQueryParameter,
3626
+ offsetQueryParameter,
3627
+ locationQueryParameter
3435
3628
  ],
3436
3629
  responses: {
3437
3630
  200: { description: "Snapshots returned." }
@@ -3442,11 +3635,44 @@ var routeCatalog = [
3442
3635
  path: "/api/v1/projects/{name}/timeline",
3443
3636
  summary: "Get keyword timeline",
3444
3637
  tags: ["history"],
3445
- parameters: [nameParameter],
3638
+ parameters: [nameParameter, locationQueryParameter],
3446
3639
  responses: {
3447
3640
  200: { description: "Timeline returned." }
3448
3641
  }
3449
3642
  },
3643
+ {
3644
+ method: "get",
3645
+ path: "/api/v1/projects/{name}/analytics/metrics",
3646
+ summary: "Get citation trend analytics",
3647
+ tags: ["analytics"],
3648
+ parameters: [nameParameter, analyticsWindowParameter],
3649
+ responses: {
3650
+ 200: { description: "Citation metrics returned." },
3651
+ 404: { description: "Project not found." }
3652
+ }
3653
+ },
3654
+ {
3655
+ method: "get",
3656
+ path: "/api/v1/projects/{name}/analytics/gaps",
3657
+ summary: "Get brand gap analysis",
3658
+ tags: ["analytics"],
3659
+ parameters: [nameParameter, analyticsWindowParameter],
3660
+ responses: {
3661
+ 200: { description: "Gap analysis returned." },
3662
+ 404: { description: "Project not found." }
3663
+ }
3664
+ },
3665
+ {
3666
+ method: "get",
3667
+ path: "/api/v1/projects/{name}/analytics/sources",
3668
+ summary: "Get source origin analytics",
3669
+ tags: ["analytics"],
3670
+ parameters: [nameParameter, analyticsWindowParameter],
3671
+ responses: {
3672
+ 200: { description: "Source breakdown returned." },
3673
+ 404: { description: "Project not found." }
3674
+ }
3675
+ },
3450
3676
  {
3451
3677
  method: "get",
3452
3678
  path: "/api/v1/projects/{name}/snapshots/diff",
@@ -3511,6 +3737,83 @@ var routeCatalog = [
3511
3737
  501: { description: "Provider updates are not supported." }
3512
3738
  }
3513
3739
  },
3740
+ {
3741
+ method: "put",
3742
+ path: "/api/v1/settings/google",
3743
+ summary: "Update Google OAuth settings",
3744
+ tags: ["settings"],
3745
+ requestBody: {
3746
+ required: true,
3747
+ content: {
3748
+ "application/json": {
3749
+ schema: {
3750
+ type: "object",
3751
+ required: ["clientId", "clientSecret"],
3752
+ properties: {
3753
+ clientId: stringSchema,
3754
+ clientSecret: stringSchema
3755
+ }
3756
+ }
3757
+ }
3758
+ }
3759
+ },
3760
+ responses: {
3761
+ 200: { description: "Google settings updated." },
3762
+ 400: { description: "Invalid Google settings." },
3763
+ 501: { description: "Google settings updates are not supported." }
3764
+ }
3765
+ },
3766
+ {
3767
+ method: "put",
3768
+ path: "/api/v1/settings/bing",
3769
+ summary: "Update Bing settings",
3770
+ tags: ["settings"],
3771
+ requestBody: {
3772
+ required: true,
3773
+ content: {
3774
+ "application/json": {
3775
+ schema: {
3776
+ type: "object",
3777
+ required: ["apiKey"],
3778
+ properties: {
3779
+ apiKey: stringSchema
3780
+ }
3781
+ }
3782
+ }
3783
+ }
3784
+ },
3785
+ responses: {
3786
+ 200: { description: "Bing settings updated." },
3787
+ 400: { description: "Invalid Bing settings." },
3788
+ 501: { description: "Bing settings updates are not supported." }
3789
+ }
3790
+ },
3791
+ {
3792
+ method: "put",
3793
+ path: "/api/v1/settings/cdp",
3794
+ summary: "Update CDP endpoint settings",
3795
+ tags: ["settings", "cdp"],
3796
+ requestBody: {
3797
+ required: true,
3798
+ content: {
3799
+ "application/json": {
3800
+ schema: {
3801
+ type: "object",
3802
+ required: ["host"],
3803
+ properties: {
3804
+ host: stringSchema,
3805
+ port: integerSchema
3806
+ }
3807
+ }
3808
+ }
3809
+ }
3810
+ },
3811
+ responses: {
3812
+ 200: { description: "CDP endpoint updated." },
3813
+ 400: { description: "Invalid CDP settings." },
3814
+ 501: { description: "CDP updates are not supported." }
3815
+ }
3816
+ },
3514
3817
  {
3515
3818
  method: "put",
3516
3819
  path: "/api/v1/projects/{name}/schedule",
@@ -3664,6 +3967,573 @@ var routeCatalog = [
3664
3967
  400: { description: "Invalid telemetry request." },
3665
3968
  501: { description: "Telemetry configuration is not available." }
3666
3969
  }
3970
+ },
3971
+ {
3972
+ method: "get",
3973
+ path: "/api/v1/screenshots/{snapshotId}",
3974
+ summary: "Fetch a stored browser screenshot",
3975
+ tags: ["cdp"],
3976
+ parameters: [snapshotIdParameter],
3977
+ responses: {
3978
+ 200: { description: "Screenshot returned." },
3979
+ 404: { description: "Screenshot not found." }
3980
+ }
3981
+ },
3982
+ {
3983
+ method: "get",
3984
+ path: "/api/v1/cdp/status",
3985
+ summary: "Get CDP connection status",
3986
+ tags: ["cdp"],
3987
+ responses: {
3988
+ 200: { description: "CDP status returned." },
3989
+ 501: { description: "CDP is not configured." }
3990
+ }
3991
+ },
3992
+ {
3993
+ method: "post",
3994
+ path: "/api/v1/cdp/screenshot",
3995
+ summary: "Run a one-off browser query and capture screenshots",
3996
+ tags: ["cdp"],
3997
+ requestBody: {
3998
+ required: true,
3999
+ content: {
4000
+ "application/json": {
4001
+ schema: {
4002
+ type: "object",
4003
+ required: ["query"],
4004
+ properties: {
4005
+ query: stringSchema,
4006
+ targets: stringArraySchema
4007
+ }
4008
+ }
4009
+ }
4010
+ }
4011
+ },
4012
+ responses: {
4013
+ 200: { description: "CDP screenshot results returned." },
4014
+ 400: { description: "Invalid CDP screenshot request." },
4015
+ 501: { description: "CDP screenshot support is not available." }
4016
+ }
4017
+ },
4018
+ {
4019
+ method: "get",
4020
+ path: "/api/v1/projects/{name}/runs/{runId}/browser-diff",
4021
+ summary: "Compare API and browser provider results for a run",
4022
+ tags: ["cdp", "runs"],
4023
+ parameters: [nameParameter, projectRunIdParameter],
4024
+ responses: {
4025
+ 200: { description: "Browser diff returned." },
4026
+ 404: { description: "Project or run not found." }
4027
+ }
4028
+ },
4029
+ {
4030
+ method: "get",
4031
+ path: "/api/v1/google/callback",
4032
+ summary: "Handle the shared Google OAuth callback",
4033
+ tags: ["google"],
4034
+ auth: false,
4035
+ parameters: [
4036
+ { name: "code", in: "query", description: "OAuth authorization code.", schema: stringSchema },
4037
+ { name: "state", in: "query", description: "Signed OAuth state payload.", schema: stringSchema },
4038
+ { name: "error", in: "query", description: "OAuth error code.", schema: stringSchema }
4039
+ ],
4040
+ responses: {
4041
+ 200: { description: "OAuth callback handled." },
4042
+ 400: { description: "Invalid callback request." },
4043
+ 500: { description: "OAuth configuration is incomplete." }
4044
+ }
4045
+ },
4046
+ {
4047
+ method: "get",
4048
+ path: "/api/v1/projects/{name}/google/callback",
4049
+ summary: "Handle the legacy project-scoped Google OAuth callback",
4050
+ tags: ["google"],
4051
+ auth: false,
4052
+ parameters: [
4053
+ nameParameter,
4054
+ { name: "code", in: "query", description: "OAuth authorization code.", schema: stringSchema },
4055
+ { name: "state", in: "query", description: "Signed OAuth state payload.", schema: stringSchema },
4056
+ { name: "error", in: "query", description: "OAuth error code.", schema: stringSchema }
4057
+ ],
4058
+ responses: {
4059
+ 200: { description: "OAuth callback handled." },
4060
+ 400: { description: "Invalid callback request." },
4061
+ 500: { description: "OAuth configuration is incomplete." }
4062
+ }
4063
+ },
4064
+ {
4065
+ method: "get",
4066
+ path: "/api/v1/projects/{name}/google/connections",
4067
+ summary: "List Google connections for a project",
4068
+ tags: ["google"],
4069
+ parameters: [nameParameter],
4070
+ responses: {
4071
+ 200: { description: "Google connections returned." },
4072
+ 404: { description: "Project not found." }
4073
+ }
4074
+ },
4075
+ {
4076
+ method: "post",
4077
+ path: "/api/v1/projects/{name}/google/connect",
4078
+ summary: "Start a Google OAuth connection flow",
4079
+ tags: ["google"],
4080
+ parameters: [nameParameter],
4081
+ requestBody: {
4082
+ required: true,
4083
+ content: {
4084
+ "application/json": {
4085
+ schema: {
4086
+ type: "object",
4087
+ required: ["type"],
4088
+ properties: {
4089
+ type: googleConnectionTypeSchema2,
4090
+ propertyId: stringSchema,
4091
+ publicUrl: stringSchema
4092
+ }
4093
+ }
4094
+ }
4095
+ }
4096
+ },
4097
+ responses: {
4098
+ 200: { description: "Google auth URL returned." },
4099
+ 400: { description: "Invalid Google connection request." }
4100
+ }
4101
+ },
4102
+ {
4103
+ method: "delete",
4104
+ path: "/api/v1/projects/{name}/google/connections/{type}",
4105
+ summary: "Delete a Google connection",
4106
+ tags: ["google"],
4107
+ parameters: [nameParameter, googleTypeParameter],
4108
+ responses: {
4109
+ 204: { description: "Google connection deleted." },
4110
+ 404: { description: "Project or connection not found." }
4111
+ }
4112
+ },
4113
+ {
4114
+ method: "get",
4115
+ path: "/api/v1/projects/{name}/google/properties",
4116
+ summary: "List available Google Search Console properties",
4117
+ tags: ["google"],
4118
+ parameters: [nameParameter],
4119
+ responses: {
4120
+ 200: { description: "Google properties returned." },
4121
+ 400: { description: "Google OAuth is not configured." },
4122
+ 404: { description: "Project not found." }
4123
+ }
4124
+ },
4125
+ {
4126
+ method: "put",
4127
+ path: "/api/v1/projects/{name}/google/connections/{type}/property",
4128
+ summary: "Set the property for a Google connection",
4129
+ tags: ["google"],
4130
+ parameters: [nameParameter, googleTypeParameter],
4131
+ requestBody: {
4132
+ required: true,
4133
+ content: {
4134
+ "application/json": {
4135
+ schema: {
4136
+ type: "object",
4137
+ required: ["propertyId"],
4138
+ properties: {
4139
+ propertyId: stringSchema
4140
+ }
4141
+ }
4142
+ }
4143
+ }
4144
+ },
4145
+ responses: {
4146
+ 200: { description: "Google property updated." },
4147
+ 400: { description: "Invalid property request." },
4148
+ 404: { description: "Project or connection not found." }
4149
+ }
4150
+ },
4151
+ {
4152
+ method: "put",
4153
+ path: "/api/v1/projects/{name}/google/connections/{type}/sitemap",
4154
+ summary: "Set the sitemap URL for a Google connection",
4155
+ tags: ["google"],
4156
+ parameters: [nameParameter, googleTypeParameter],
4157
+ requestBody: {
4158
+ required: true,
4159
+ content: {
4160
+ "application/json": {
4161
+ schema: {
4162
+ type: "object",
4163
+ required: ["sitemapUrl"],
4164
+ properties: {
4165
+ sitemapUrl: stringSchema
4166
+ }
4167
+ }
4168
+ }
4169
+ }
4170
+ },
4171
+ responses: {
4172
+ 200: { description: "Google sitemap updated." },
4173
+ 400: { description: "Invalid sitemap request." },
4174
+ 404: { description: "Project or connection not found." }
4175
+ }
4176
+ },
4177
+ {
4178
+ method: "post",
4179
+ path: "/api/v1/projects/{name}/google/gsc/sync",
4180
+ summary: "Queue a GSC sync run",
4181
+ tags: ["google"],
4182
+ parameters: [nameParameter],
4183
+ requestBody: {
4184
+ content: {
4185
+ "application/json": {
4186
+ schema: {
4187
+ type: "object",
4188
+ properties: {
4189
+ days: integerSchema,
4190
+ full: booleanSchema
4191
+ }
4192
+ }
4193
+ }
4194
+ }
4195
+ },
4196
+ responses: {
4197
+ 200: { description: "GSC sync run returned." },
4198
+ 400: { description: "Invalid GSC sync request." },
4199
+ 404: { description: "Project or connection not found." }
4200
+ }
4201
+ },
4202
+ {
4203
+ method: "get",
4204
+ path: "/api/v1/projects/{name}/google/gsc/performance",
4205
+ summary: "Get GSC search performance data",
4206
+ tags: ["google"],
4207
+ parameters: [
4208
+ nameParameter,
4209
+ { name: "startDate", in: "query", description: "Filter by start date.", schema: stringSchema },
4210
+ { name: "endDate", in: "query", description: "Filter by end date.", schema: stringSchema },
4211
+ { name: "query", in: "query", description: "Filter by search query.", schema: stringSchema },
4212
+ { name: "page", in: "query", description: "Filter by page URL.", schema: stringSchema },
4213
+ limitQueryParameter
4214
+ ],
4215
+ responses: {
4216
+ 200: { description: "GSC performance rows returned." },
4217
+ 404: { description: "Project not found." }
4218
+ }
4219
+ },
4220
+ {
4221
+ method: "post",
4222
+ path: "/api/v1/projects/{name}/google/gsc/inspect",
4223
+ summary: "Inspect a URL through Google Search Console",
4224
+ tags: ["google"],
4225
+ parameters: [nameParameter],
4226
+ requestBody: {
4227
+ required: true,
4228
+ content: {
4229
+ "application/json": {
4230
+ schema: {
4231
+ type: "object",
4232
+ required: ["url"],
4233
+ properties: {
4234
+ url: stringSchema
4235
+ }
4236
+ }
4237
+ }
4238
+ }
4239
+ },
4240
+ responses: {
4241
+ 200: { description: "GSC inspection result returned." },
4242
+ 400: { description: "Invalid inspection request." },
4243
+ 404: { description: "Project or connection not found." }
4244
+ }
4245
+ },
4246
+ {
4247
+ method: "get",
4248
+ path: "/api/v1/projects/{name}/google/gsc/inspections",
4249
+ summary: "List GSC URL inspections",
4250
+ tags: ["google"],
4251
+ parameters: [nameParameter, { name: "url", in: "query", description: "Filter by URL.", schema: stringSchema }, limitQueryParameter],
4252
+ responses: {
4253
+ 200: { description: "GSC inspections returned." },
4254
+ 404: { description: "Project not found." }
4255
+ }
4256
+ },
4257
+ {
4258
+ method: "get",
4259
+ path: "/api/v1/projects/{name}/google/gsc/deindexed",
4260
+ summary: "List GSC deindexed pages",
4261
+ tags: ["google"],
4262
+ parameters: [nameParameter],
4263
+ responses: {
4264
+ 200: { description: "Deindexed pages returned." },
4265
+ 404: { description: "Project not found." }
4266
+ }
4267
+ },
4268
+ {
4269
+ method: "get",
4270
+ path: "/api/v1/projects/{name}/google/gsc/coverage",
4271
+ summary: "Get GSC coverage summary",
4272
+ tags: ["google"],
4273
+ parameters: [nameParameter],
4274
+ responses: {
4275
+ 200: { description: "GSC coverage returned." },
4276
+ 404: { description: "Project not found." }
4277
+ }
4278
+ },
4279
+ {
4280
+ method: "get",
4281
+ path: "/api/v1/projects/{name}/google/gsc/coverage/history",
4282
+ summary: "Get GSC coverage history",
4283
+ tags: ["google"],
4284
+ parameters: [nameParameter, limitQueryParameter],
4285
+ responses: {
4286
+ 200: { description: "GSC coverage history returned." },
4287
+ 404: { description: "Project not found." }
4288
+ }
4289
+ },
4290
+ {
4291
+ method: "get",
4292
+ path: "/api/v1/projects/{name}/google/gsc/sitemaps",
4293
+ summary: "List GSC sitemaps",
4294
+ tags: ["google"],
4295
+ parameters: [nameParameter],
4296
+ responses: {
4297
+ 200: { description: "GSC sitemaps returned." },
4298
+ 400: { description: "Invalid sitemap request." },
4299
+ 404: { description: "Project or connection not found." }
4300
+ }
4301
+ },
4302
+ {
4303
+ method: "post",
4304
+ path: "/api/v1/projects/{name}/google/gsc/discover-sitemaps",
4305
+ summary: "Discover sitemaps and queue sitemap inspection",
4306
+ tags: ["google"],
4307
+ parameters: [nameParameter],
4308
+ responses: {
4309
+ 200: { description: "Discovered sitemaps and queued run returned." },
4310
+ 400: { description: "Invalid sitemap discovery request." },
4311
+ 404: { description: "Project or connection not found." }
4312
+ }
4313
+ },
4314
+ {
4315
+ method: "post",
4316
+ path: "/api/v1/projects/{name}/google/gsc/inspect-sitemap",
4317
+ summary: "Queue a sitemap inspection run",
4318
+ tags: ["google"],
4319
+ parameters: [nameParameter],
4320
+ requestBody: {
4321
+ content: {
4322
+ "application/json": {
4323
+ schema: {
4324
+ type: "object",
4325
+ properties: {
4326
+ sitemapUrl: stringSchema
4327
+ }
4328
+ }
4329
+ }
4330
+ }
4331
+ },
4332
+ responses: {
4333
+ 200: { description: "Sitemap inspection run returned." },
4334
+ 400: { description: "Invalid sitemap inspection request." },
4335
+ 404: { description: "Project or connection not found." }
4336
+ }
4337
+ },
4338
+ {
4339
+ method: "post",
4340
+ path: "/api/v1/projects/{name}/google/indexing/request",
4341
+ summary: "Request Google indexing notifications",
4342
+ tags: ["google"],
4343
+ parameters: [nameParameter],
4344
+ requestBody: {
4345
+ required: true,
4346
+ content: {
4347
+ "application/json": {
4348
+ schema: {
4349
+ type: "object",
4350
+ properties: {
4351
+ urls: stringArraySchema,
4352
+ allUnindexed: booleanSchema
4353
+ }
4354
+ }
4355
+ }
4356
+ }
4357
+ },
4358
+ responses: {
4359
+ 200: { description: "Indexing request results returned." },
4360
+ 400: { description: "Invalid indexing request." },
4361
+ 404: { description: "Project or connection not found." }
4362
+ }
4363
+ },
4364
+ {
4365
+ method: "post",
4366
+ path: "/api/v1/projects/{name}/bing/connect",
4367
+ summary: "Connect Bing Webmaster Tools",
4368
+ tags: ["bing"],
4369
+ parameters: [nameParameter],
4370
+ requestBody: {
4371
+ required: true,
4372
+ content: {
4373
+ "application/json": {
4374
+ schema: {
4375
+ type: "object",
4376
+ required: ["apiKey"],
4377
+ properties: {
4378
+ apiKey: stringSchema
4379
+ }
4380
+ }
4381
+ }
4382
+ }
4383
+ },
4384
+ responses: {
4385
+ 200: { description: "Bing connection returned." },
4386
+ 400: { description: "Invalid Bing connection request." },
4387
+ 404: { description: "Project not found." }
4388
+ }
4389
+ },
4390
+ {
4391
+ method: "delete",
4392
+ path: "/api/v1/projects/{name}/bing/disconnect",
4393
+ summary: "Disconnect Bing Webmaster Tools",
4394
+ tags: ["bing"],
4395
+ parameters: [nameParameter],
4396
+ responses: {
4397
+ 204: { description: "Bing connection deleted." },
4398
+ 404: { description: "Project or connection not found." }
4399
+ }
4400
+ },
4401
+ {
4402
+ method: "get",
4403
+ path: "/api/v1/projects/{name}/bing/status",
4404
+ summary: "Get Bing connection status",
4405
+ tags: ["bing"],
4406
+ parameters: [nameParameter],
4407
+ responses: {
4408
+ 200: { description: "Bing status returned." },
4409
+ 404: { description: "Project not found." }
4410
+ }
4411
+ },
4412
+ {
4413
+ method: "get",
4414
+ path: "/api/v1/projects/{name}/bing/sites",
4415
+ summary: "List Bing sites for the current connection",
4416
+ tags: ["bing"],
4417
+ parameters: [nameParameter],
4418
+ responses: {
4419
+ 200: { description: "Bing sites returned." },
4420
+ 400: { description: "Bing is not configured for this project." },
4421
+ 404: { description: "Project not found." }
4422
+ }
4423
+ },
4424
+ {
4425
+ method: "post",
4426
+ path: "/api/v1/projects/{name}/bing/set-site",
4427
+ summary: "Set the active Bing site",
4428
+ tags: ["bing"],
4429
+ parameters: [nameParameter],
4430
+ requestBody: {
4431
+ required: true,
4432
+ content: {
4433
+ "application/json": {
4434
+ schema: {
4435
+ type: "object",
4436
+ required: ["siteUrl"],
4437
+ properties: {
4438
+ siteUrl: stringSchema
4439
+ }
4440
+ }
4441
+ }
4442
+ }
4443
+ },
4444
+ responses: {
4445
+ 200: { description: "Active Bing site updated." },
4446
+ 400: { description: "Invalid Bing site request." },
4447
+ 404: { description: "Project or connection not found." }
4448
+ }
4449
+ },
4450
+ {
4451
+ method: "get",
4452
+ path: "/api/v1/projects/{name}/bing/coverage",
4453
+ summary: "Get Bing index coverage",
4454
+ tags: ["bing"],
4455
+ parameters: [nameParameter],
4456
+ responses: {
4457
+ 200: { description: "Bing coverage returned." },
4458
+ 400: { description: "Bing is not configured for this project." },
4459
+ 404: { description: "Project not found." }
4460
+ }
4461
+ },
4462
+ {
4463
+ method: "get",
4464
+ path: "/api/v1/projects/{name}/bing/inspections",
4465
+ summary: "List Bing URL inspections",
4466
+ tags: ["bing"],
4467
+ parameters: [nameParameter, { name: "url", in: "query", description: "Filter by URL.", schema: stringSchema }, limitQueryParameter],
4468
+ responses: {
4469
+ 200: { description: "Bing inspections returned." },
4470
+ 400: { description: "Bing is not configured for this project." },
4471
+ 404: { description: "Project not found." }
4472
+ }
4473
+ },
4474
+ {
4475
+ method: "post",
4476
+ path: "/api/v1/projects/{name}/bing/inspect-url",
4477
+ summary: "Inspect a URL through Bing Webmaster Tools",
4478
+ tags: ["bing"],
4479
+ parameters: [nameParameter],
4480
+ requestBody: {
4481
+ required: true,
4482
+ content: {
4483
+ "application/json": {
4484
+ schema: {
4485
+ type: "object",
4486
+ required: ["url"],
4487
+ properties: {
4488
+ url: stringSchema
4489
+ }
4490
+ }
4491
+ }
4492
+ }
4493
+ },
4494
+ responses: {
4495
+ 200: { description: "Bing inspection result returned." },
4496
+ 400: { description: "Invalid inspection request." },
4497
+ 404: { description: "Project or connection not found." }
4498
+ }
4499
+ },
4500
+ {
4501
+ method: "post",
4502
+ path: "/api/v1/projects/{name}/bing/request-indexing",
4503
+ summary: "Submit URLs to Bing for indexing",
4504
+ tags: ["bing"],
4505
+ parameters: [nameParameter],
4506
+ requestBody: {
4507
+ required: true,
4508
+ content: {
4509
+ "application/json": {
4510
+ schema: {
4511
+ type: "object",
4512
+ properties: {
4513
+ urls: stringArraySchema,
4514
+ allUnindexed: booleanSchema
4515
+ }
4516
+ }
4517
+ }
4518
+ }
4519
+ },
4520
+ responses: {
4521
+ 200: { description: "Bing indexing request results returned." },
4522
+ 400: { description: "Invalid indexing request." },
4523
+ 404: { description: "Project or connection not found." }
4524
+ }
4525
+ },
4526
+ {
4527
+ method: "get",
4528
+ path: "/api/v1/projects/{name}/bing/performance",
4529
+ summary: "Get Bing keyword performance",
4530
+ tags: ["bing"],
4531
+ parameters: [nameParameter, limitQueryParameter],
4532
+ responses: {
4533
+ 200: { description: "Bing performance returned." },
4534
+ 400: { description: "Bing is not configured for this project." },
4535
+ 404: { description: "Project not found." }
4536
+ }
3667
4537
  }
3668
4538
  ];
3669
4539
  function buildOpenApiDocument(info = {}) {
@@ -3688,7 +4558,7 @@ function buildOpenApiDocument(info = {}) {
3688
4558
  info: {
3689
4559
  title: info.title ?? "Canonry API",
3690
4560
  version: info.version ?? "0.0.0",
3691
- description: info.description ?? "REST API for Canonry projects, runs, schedules, and notifications."
4561
+ description: info.description ?? "REST API for Canonry projects, runs, analytics, integrations, and operator workflows."
3692
4562
  },
3693
4563
  servers: [
3694
4564
  {