@nuxtseo/protocol 0.5.10 → 0.5.11

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.
@@ -288,9 +288,224 @@ export const analyticsQueryOperation = defineOperation({
288
288
  }],
289
289
  },
290
290
  });
291
+ export const botActivityParamsSchema = schema.strictObject({
292
+ siteId: schema.string().min(1),
293
+ });
294
+ export const botActivityQuerySchema = schema.strictObject({
295
+ days: schema.coerce.number().int().min(1).max(90).default(28),
296
+ crawler: schema.enum(['googlebot', 'bingbot']).default('googlebot'),
297
+ });
298
+ const googlebotStatusCountsSchema = defineResponseObject({
299
+ '2xx': schema.number(),
300
+ '304': schema.number(),
301
+ 'other3xx': schema.number(),
302
+ '404': schema.number(),
303
+ '410': schema.number(),
304
+ 'other4xx': schema.number(),
305
+ '429': schema.number(),
306
+ '5xx': schema.number(),
307
+ });
308
+ const googlebotContentCountsSchema = defineResponseObject({
309
+ html: schema.number(),
310
+ js: schema.number(),
311
+ css: schema.number(),
312
+ image: schema.number(),
313
+ other: schema.number(),
314
+ });
315
+ const googlebotOriginSchema = defineResponseObject({
316
+ requests: schema.number(),
317
+ avgMs: schema.number().nullable(),
318
+ p50Ms: schema.number().nullable(),
319
+ });
320
+ const googlebotDaySchema = defineResponseObject({
321
+ day: schema.string(),
322
+ complete: schema.boolean(),
323
+ requests: schema.number(),
324
+ bytes: schema.number(),
325
+ statuses: googlebotStatusCountsSchema.producer,
326
+ contents: googlebotContentCountsSchema.producer,
327
+ cacheHits: schema.number(),
328
+ origin: googlebotOriginSchema.producer,
329
+ }, {
330
+ day: schema.string(),
331
+ complete: schema.boolean(),
332
+ requests: schema.number(),
333
+ bytes: schema.number(),
334
+ statuses: googlebotStatusCountsSchema.client,
335
+ contents: googlebotContentCountsSchema.client,
336
+ cacheHits: schema.number(),
337
+ origin: googlebotOriginSchema.client,
338
+ });
339
+ const googlebotErrorPathSchema = defineResponseObject({
340
+ path: schema.string(),
341
+ status: schema.number(),
342
+ requests: schema.number(),
343
+ lastDay: schema.string(),
344
+ });
345
+ const googlebotCollectionSchema = defineResponseObject({
346
+ fromDay: schema.string(),
347
+ toDay: schema.string(),
348
+ completeThroughDay: schema.string().nullable(),
349
+ });
350
+ const googlebotTotalsSchema = defineResponseObject({
351
+ requests: schema.number(),
352
+ statuses: googlebotStatusCountsSchema.producer,
353
+ contents: googlebotContentCountsSchema.producer,
354
+ cacheHitShare: schema.number().nullable(),
355
+ wastedShare: schema.number().nullable(),
356
+ originAvgMs: schema.number().nullable(),
357
+ }, {
358
+ requests: schema.number(),
359
+ statuses: googlebotStatusCountsSchema.client,
360
+ contents: googlebotContentCountsSchema.client,
361
+ cacheHitShare: schema.number().nullable(),
362
+ wastedShare: schema.number().nullable(),
363
+ originAvgMs: schema.number().nullable(),
364
+ });
365
+ /**
366
+ * Whether the stored days are still current. `zone_access_missing` names the
367
+ * exact Cloudflare grant: an account analytics token cannot read zone
368
+ * analytics, and an OAuth user can withhold that scope.
369
+ */
370
+ const googlebotAccessSchema = {
371
+ producer: schema.discriminatedUnion('_tag', [
372
+ schema.strictObject({ _tag: schema.literal('collecting') }),
373
+ schema.strictObject({
374
+ _tag: schema.literal('zone_access_missing'),
375
+ permissions: schema.array(schema.string()),
376
+ oauthScopes: schema.array(schema.string()),
377
+ message: schema.string(),
378
+ }),
379
+ schema.strictObject({ _tag: schema.literal('collection_failed'), reason: schema.string(), message: schema.string() }),
380
+ ]),
381
+ client: schema.discriminatedUnion('_tag', [
382
+ schema.looseObject({ _tag: schema.literal('collecting') }),
383
+ schema.looseObject({
384
+ _tag: schema.literal('zone_access_missing'),
385
+ permissions: schema.array(schema.string()),
386
+ oauthScopes: schema.array(schema.string()),
387
+ message: schema.string(),
388
+ }),
389
+ schema.looseObject({ _tag: schema.literal('collection_failed'), reason: schema.string(), message: schema.string() }),
390
+ ]),
391
+ };
392
+ export const botActivityDataSchema = defineResponseObject({
393
+ crawler: schema.enum(['googlebot', 'bingbot']),
394
+ access: googlebotAccessSchema.producer,
395
+ collection: googlebotCollectionSchema.producer.nullable(),
396
+ totals: googlebotTotalsSchema.producer,
397
+ days: schema.array(googlebotDaySchema.producer),
398
+ errorPaths: schema.array(googlebotErrorPathSchema.producer),
399
+ }, {
400
+ crawler: schema.enum(['googlebot', 'bingbot']),
401
+ access: googlebotAccessSchema.client,
402
+ collection: googlebotCollectionSchema.client.nullable(),
403
+ totals: googlebotTotalsSchema.client,
404
+ days: schema.array(googlebotDaySchema.client),
405
+ errorPaths: schema.array(googlebotErrorPathSchema.client),
406
+ });
407
+ export const botActivityResponseSchema = defineSuccessEnvelope(botActivityDataSchema);
408
+ export const botActivityOperation = defineOperation({
409
+ id: 'analytics.bots.activity',
410
+ method: 'GET',
411
+ path: '/api/v1/sites/{siteId}/bot-activity',
412
+ visibility: 'public',
413
+ semantics: {
414
+ kind: 'query',
415
+ sideEffects: 'none',
416
+ retry: 'idempotent',
417
+ readConsistency: 'replica-ok',
418
+ },
419
+ auth: {
420
+ credentials: ['session', 'api_token'],
421
+ scopes: ['analytics:read'],
422
+ minimumRole: 'viewer',
423
+ ownership: 'site',
424
+ },
425
+ request: {
426
+ params: botActivityParamsSchema,
427
+ query: botActivityQuerySchema,
428
+ headers: null,
429
+ body: null,
430
+ },
431
+ responses: { 200: botActivityResponseSchema },
432
+ errors: [
433
+ 'invalid_request',
434
+ 'unauthorized',
435
+ 'auth_expired',
436
+ 'forbidden',
437
+ 'not_found',
438
+ 'entitlement_required',
439
+ 'conflict',
440
+ 'rate_limited',
441
+ 'contract_violation',
442
+ 'internal_error',
443
+ ],
444
+ errorResponse: publicV1ErrorEnvelope,
445
+ resources: {
446
+ reads: [{ type: 'site.analytics', idFrom: 'params.siteId' }],
447
+ changes: [],
448
+ },
449
+ policy: {
450
+ rate: publicV1StandardRatePolicy,
451
+ idempotency: { _tag: 'none' },
452
+ entitlement: 'growth',
453
+ meter: null,
454
+ },
455
+ lifecycle: { introduced: '1.3.0' },
456
+ docs: {
457
+ summary: 'Read Bot Traffic',
458
+ description: [
459
+ 'Reads verified Googlebot or Bingbot requests to an owned Site, counted per UTC day at its Cloudflare zone.',
460
+ 'Cloudflare verifies the crawler. Search Console serves Crawl Stats only in its UI, so these are edge counts without a discovery or refresh split.',
461
+ '`days` lists only days a collection covered. A missing day was never collected.',
462
+ '`access` says whether collection still runs. `zone_access_missing` lists the Cloudflare permissions and OAuth scopes to grant; until then the stored days stop at the last collection.',
463
+ '`wastedShare` is the share of requests answered with anything except 2xx or 304.',
464
+ '`errorPaths` lists the most requested 4xx, 429 and 5xx paths. It leaves out 410.',
465
+ ].join(' '),
466
+ tags: ['Web Analytics'],
467
+ examples: [{
468
+ request: {
469
+ params: { siteId: 'site_01JXYZ' },
470
+ query: { days: 7 },
471
+ },
472
+ response: {
473
+ status: 200,
474
+ body: {
475
+ data: {
476
+ crawler: 'googlebot',
477
+ access: { _tag: 'collecting' },
478
+ collection: { fromDay: '2026-09-16', toDay: '2026-10-13', completeThroughDay: '2026-10-12' },
479
+ totals: {
480
+ requests: 2219,
481
+ statuses: { '2xx': 1933, '304': 0, 'other3xx': 60, '404': 51, '410': 1, 'other4xx': 172, '429': 0, '5xx': 2 },
482
+ contents: { html: 1702, js: 0, css: 0, image: 341, other: 176 },
483
+ cacheHitShare: 0.41,
484
+ wastedShare: 0.1289,
485
+ originAvgMs: 284,
486
+ },
487
+ days: [{
488
+ day: '2026-10-12',
489
+ complete: true,
490
+ requests: 322,
491
+ bytes: 9_840_211,
492
+ statuses: { '2xx': 282, '304': 0, 'other3xx': 6, '404': 6, '410': 0, 'other4xx': 27, '429': 0, '5xx': 1 },
493
+ contents: { html: 250, js: 0, css: 0, image: 47, other: 25 },
494
+ cacheHits: 131,
495
+ origin: { requests: 191, avgMs: 302, p50Ms: 188 },
496
+ }],
497
+ errorPaths: [{ path: '/skills/acme/retired-skill', status: 404, requests: 6, lastDay: '2026-10-12' }],
498
+ },
499
+ meta: { requestId: 'req_01JXYZ', version: '1.0' },
500
+ },
501
+ },
502
+ }],
503
+ },
504
+ });
291
505
  export const analyticsV1Registry = defineRegistry({
292
506
  feature: 'analytics',
293
507
  operations: {
294
508
  query: analyticsQueryOperation,
509
+ botActivity: botActivityOperation,
295
510
  },
296
511
  });
package/dist/v1/sites.js CHANGED
@@ -224,6 +224,7 @@ const siteStatusCountsShape = {
224
224
  */
225
225
  const siteStatusDataQualityShape = {
226
226
  status: schema.enum(['complete', 'degraded', 'unavailable']),
227
+ /** Recorded unavailable stage. Null when the assessment has no recorded cause. */
227
228
  reason: schema.string().nullable(),
228
229
  unavailableProofs: schema.array(schema.string()),
229
230
  };