# SiteVerdict — Search Console & GA4 Analyst (`siteverdict/siteverdict-gsc-ga4-analyst`) Actor

Find out why Google traffic dropped. Compares two periods in Search Console + GA4 with your own read-only Google access: ranking lost vs CTR drop vs demand vs deindexed (URL Inspection), quick wins, cannibalisation, tracking/consent red flags, top 10 actions.

- **URL**: https://apify.com/siteverdict/siteverdict-gsc-ga4-analyst.md
- **Developed by:** [Aman Thakran](https://apify.com/siteverdict) (community)
- **Categories:** Marketing, Developer tools
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

Pay per event

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## SiteVerdict — Search Console & GA4 Analyst

**Find out why your Google traffic dropped, and what to do first, in about a minute.**

This Actor reads your **Google Search Console** and **Google Analytics 4** data through Google's official APIs, using your own read-only access. It compares two periods and writes up the result the way an SEO analyst would:

- **Why clicks fell, page by page.** Each losing page gets a cause: **ranking lost**, **CTR dropped**, **demand fell** or **page deindexed**. Deindexing is confirmed with Google's **URL Inspection API**.
- **Quick wins.** Queries ranking 5–20 with real impressions, and pages whose CTR is far below what their position should earn.
- **Keyword cannibalisation.** Queries where two or more of your URLs split the impressions.
- **Sitemap and index coverage summary.**
- **GA4 performance** by channel, landing page, device and country, joined to Search Console landing pages. It also estimates the revenue at stake on the pages that lost clicks.
- **Tracking and consent red flags.** For example, GA4 organic sessions collapse while Google clicks hold steady, and the Actor finds the day it broke.
- **Top 10 actions**, a plain-English summary, evidence tables for every finding, and a shareable **HTML report**.

Everything is **deterministic and rule-based**: no AI guessing and no invented numbers. Every finding shows the data behind it.

> Try it free first. With **Google access = Sample data**, it runs on a built-in fictional shop that has every problem planted, so you can see the full report before connecting your own account.

### Who it is for

- **SEO consultants and agencies.** Diagnose a client's traffic drop and get a prioritised fix list. You can run it on a schedule for every client property.
- **In-house marketers.** Answer "why did organic drop?" before the Monday meeting.
- **Developers and analysts.** Get a clean JSON of GSC + GA4 deltas for dashboards, or for AI agents through the SiteVerdict MCP server.

### What you get

| Output | Where |
|---|---|
| One dataset item per property: summary, headline metrics, drop causes, top 10 actions, all findings with evidence tables, URL Inspection results, sitemap summary, data notes | Dataset (views: Overview, Top 10 actions, All findings, Traffic-drop causes) |
| HTML report covering all properties | Key-value store `REPORT` |
| HTML report per property | `report-<property>` |
| Full tables (all losing/gaining pages and queries, countries, devices, daily series, GA4 breakdowns, the GSC↔GA4 join) | `data-<property>` (JSON) |
| Short machine summary | `OUTPUT` |

#### How causes are decided (transparent rules)

Only the biggest losing pages are classified: up to 25, ordered by clicks lost.

| Cause | Rule | Confidence |
|---|---|---|
| **Page deindexed / not indexed** | URL Inspection verdict is not PASS, indexing is blocked (noindex), or Google chose a different canonical | confirmed |
| | Impressions collapsed by 90% or more and the URL was not inspected | suspected |
| **Ranking lost** | Average position worsened (by at least 1, or 25% of the old position, in the top 10; by at least 3 below that) | likely |
| **CTR dropped** | CTR fell 20% or more while position and impressions held | likely |
| **Demand fell** | Impressions fell 20% or more at a stable position. Query-level demand drops are listed too | likely |

Each page also shows how much of its loss came from impressions and how much from CTR.

#### Tracking and consent sanity checks

Search Console counts clicks on Google's side, so your tags don't affect it. GA4 depends on your tag and your consent banner. Comparing the two catches measurement problems:

- GA4 organic sessions per Google click fall 30% or more between periods. Possible broken tag, consent change or GTM publish.
- The **day it happened**: the 7-day window where sessions per click collapsed while Google clicks stayed within ±20%.
- Landing pages with 30 or more Google clicks but **zero** GA4 organic sessions (tag missing on that template).
- High shares of "(not set)" landing pages or the "Unassigned" channel. A Direct share jump while organic falls.
- Key events or revenue collapsing while sessions hold. GA4 Realtime showing 0 users on a busy site.

To confirm a consent issue, run **SiteVerdict — Consent Mode v2 & Tracking Auditor** on the site. It loads the page with no click, after Reject and after Accept, and decodes Consent Mode v2.

#### The CTR curve is a benchmark

Opportunity sizes use an approximate industry-average CTR by position, compiled from public CTR studies. Real SERPs with ads, AI Overviews, featured snippets or shopping units earn less. The report also shows **your own site's median CTR per position** next to it, and you can supply your own curve (`ctrCurve`).

### Setup: connect your own Google data (about 10 minutes, once)

You need a Google account that can already see the site in Search Console and GA4. The Actor only ever asks for **read-only** scopes (`webmasters.readonly`, `analytics.readonly`).

#### Option A: Service account (recommended; nothing expires)

1. **Create or choose a Google Cloud project.** Go to https://console.cloud.google.com/ and use the project picker, then **New project**. The free tier is fine and no billing is needed for these APIs.
2. **Enable the two APIs.** Go to **APIs & Services > Library**. Search for **"Google Search Console API"** and click **Enable**. Then search for **"Google Analytics Data API"** and click **Enable**.
3. **Create the service account.** Go to **IAM & Admin > Service accounts > + Create service account**. Name it, for example, `siteverdict-reader`, then click **Create and continue > Done**. It needs no project roles.
4. **Create a key.** Open the service account, then go to **Keys > Add key > Create new key > JSON > Create**. A `.json` file downloads. Copy its `client_email` (looks like `siteverdict-reader@your-project.iam.gserviceaccount.com`).
5. **Give it Search Console access.** In Search Console, select the property, then go to **Settings > Users and permissions > Add user**. Paste the `client_email` and choose **Restricted** (read-only is enough), then **Add**. Repeat for each property.
6. **Give it GA4 access.** In GA4 go to **Admin > (Property column) Property access management > + > Add users**. Paste the `client_email`, choose the role **Viewer** and untick "Notify new users". Click **Add**.
7. **Find the GA4 Property ID.** Go to **GA4 Admin > Property details**. It is the number, for example `123456789`, **not** the `G-…` measurement ID.
8. **Run the Actor.** Set **Google access = Service account** and paste the whole JSON file into **Service-account JSON key**. Under **Properties**, add for example:

```json
[{ "label": "My shop", "gscSiteUrl": "sc-domain:example.com", "ga4PropertyId": "123456789" }]
```

`gscSiteUrl` accepts `sc-domain:example.com` (Domain property), `https://www.example.com/` (URL-prefix property) or just `example.com`. The Actor matches it against the properties the service account can see and lists them if it can't find yours.

#### Option B: OAuth client and refresh token (use your own Google login)

1. In Google Cloud, enable the two APIs (steps 1–2 above).
2. Set up the consent screen. Go to **APIs & Services > OAuth consent screen**: choose External, fill the app name and your email, and add yourself as a test user. To stop refresh tokens expiring after 7 days, click **Publish app** ("In production"). For your own use you can click through the "unverified app" warning.
3. Create the client. Go to **APIs & Services > Credentials > + Create credentials > OAuth client ID > Web application**. Add the authorised redirect URI `https://developers.google.com/oauthplayground` and copy the **client ID** and **client secret**.
4. Get a refresh token from the OAuth Playground:
   - Open https://developers.google.com/oauthplayground. Click the gear icon, tick **Use your own OAuth credentials** and paste the client ID and secret.
   - In Step 1, enter the scopes `https://www.googleapis.com/auth/webmasters.readonly https://www.googleapis.com/auth/analytics.readonly` and click **Authorize APIs**. Sign in.
   - In Step 2, click **Exchange authorization code for tokens** and copy the **Refresh token**.
5. Run the Actor with **Google access = OAuth** and paste the client ID, client secret and refresh token.

#### Security and privacy

- All credential fields are **secret inputs**. Apify stores them encrypted, and the Actor uses them in memory only to get short-lived access tokens. They are never logged or written to the dataset, key-value store or report.
- Read-only scopes only. The Actor cannot change anything in Search Console or GA4.
- You can revoke access at any time. Delete the service-account key in Google Cloud, or remove the user in Search Console / GA4.
- Only aggregated metrics are requested: no user IDs, client IDs or user-level data. Search Console queries are aggregated and anonymised by Google. If a query ever contains a personal name, treat the output as you would your Search Console export (UK GDPR applies to your own use).

### Input

| Field | Default | Notes |
|---|---|---|
| `authMethod` | `sample` | `sample`, `serviceAccount` or `oauth` |
| `serviceAccountJson` / `oauthClientId` / `oauthClientSecret` / `oauthRefreshToken` | – | Secret |
| `properties` | – | `[{label, gscSiteUrl, ga4PropertyId}]`, up to 50 per run. Either id may be omitted |
| `comparison` | `previous_period` | Also `year_over_year` (364 days back, weekday-aligned) or `custom` |
| `periodDays` | 28 | 7–180 |
| `dataLagDays` | 3 | Search Console data is final after 2–3 days |
| `currentStartDate`…`previousEndDate` | – | For `custom` |
| `searchType` | `web` | Also `image`, `video`, `news`, `discover` or `googleNews` |
| `brandTerms` | \[] | Brand queries are excluded from opportunities and split out |
| `minImpressions` | 100 | Threshold for opportunities and cannibalisation |
| `maxUrlInspections` | 10 | Losing pages inspected per property (billed per URL) |
| `includeGa4Realtime` | true | One runRealtimeReport call |
| `dataState` | `final` | `all` includes fresh data |
| `maxRowsPerQuery` | 25,000 | Per report, paged |
| `maxTableRows` | 25 | Rows per evidence table |
| `ctrCurve` | built-in | Your own CTR by position |

### Example output (abridged)

```json
{
  "label": "My shop",
  "status": "ok",
  "gsc": { "clicks": { "previous": 11380, "current": 7845, "changePct": -31.1 }, "avgPosition": { "previous": 6.3, "current": 6.8, "changePct": 7.9 } },
  "ga4": { "sessions": { "previous": 18023, "current": 10815, "changePct": -40 }, "organicSessions": { "previous": 9230, "current": 4228, "changePct": -54.2 }, "currency": "GBP" },
  "dropCauses": [
    { "cause": "page_deindexed", "causeLabel": "Page deindexed / not indexed", "pages": 1, "clicksLost": 875 },
    { "cause": "ranking_lost", "causeLabel": "Ranking lost", "pages": 1, "clicksLost": 965 }
  ],
  "topActions": [
    { "rank": 1, "priority": "critical", "action": "Check GA4 tracking before changing any content: GA4 organic sessions changed -54.2% while Google clicks changed -31.1%. ..." },
    { "rank": 2, "priority": "critical", "action": "Restore indexing on 1 page(s) (top: https://www.example.com/hiking-boots/) - ..." }
  ],
  "findings": [ { "id": "deindexed-pages", "severity": "critical", "title": "...", "evidence": { "columns": ["Page", "..."], "rows": [["https://www.example.com/hiking-boots/", 912, 37, "..."]] } } ],
  "reportUrl": "https://api.apify.com/v2/key-value-stores/.../records/report-my-shop"
}
```

The full sample run is in `docs/sample-output.json` and `docs/sample-report.html`.

### Pricing

Pay per event:

- **`property-analysed`**: once per property per run. It is only charged after real data has been fetched; errors and empty results are free.
- **`url-inspected`**: per successful URL Inspection call.

Sample mode is free apart from the Apify start event. The Actor stops before it would exceed your run's **maximum total charge**: inspections are skipped and remaining properties are not started. See PRICING.md for the proposed prices.

### Limits and quotas (Google, checked 11 Oct 2026)

Calls run on **your** Google Cloud project's quota, not a shared one.

| API | Limit | What this Actor uses |
|---|---|---|
| Search Analytics | 1,200 queries/min per site and per user; short-term load quota in 10-minute windows. At most 50,000 rows per day per search type are exposed; `rowLimit` max 25,000 | About 13 queries per property plus paging |
| URL Inspection | 2,000/day and 600/min per site | `maxUrlInspections` (default 10), at most 400/min |
| GA4 Data API (standard property) | 200,000 tokens/day, 40,000/hour, 14,000 per project per property per hour, 10 concurrent requests | 14 runReports (≤7 in parallel) plus 1 realtime |

Sources: https://developers.google.com/webmaster-tools/limits · https://developers.google.com/webmaster-tools/v1/how-tos/all-your-data · https://developers.google.com/analytics/devguides/reporting/data/v1/quotas

**Other limits:**

- Search Console hides rare queries ("anonymised"). The report shows the share of clicks with no visible query.
- GA4 may threshold, sample or group rows into "(other)". These cases are flagged in `notes`.
- The GSC↔GA4 join matches on URL path. GA4 has no hostname in `landingPage`, so multi-host Domain properties are joined on the host with the most clicks.
- GA4 "Organic Search" includes Bing and other engines, so treat sessions per click as a sanity ratio.
- Sitemap "indexed" counts are deprecated in the API. Coverage uses pages with impressions plus URL Inspection samples as a proxy.

### FAQ

**Does it need my Google password?** No. It uses a service-account key or an OAuth refresh token that you create and can revoke.

**Will it change anything in my accounts?** No. It uses read-only scopes only.

**Why does it say it "cannot see" my property?** The service-account email has not been added in Search Console (Settings > Users and permissions), or you typed a different property form. The error lists the properties it can see.

**It says the API is not enabled.** Enable "Google Search Console API" and "Google Analytics Data API" in the same Cloud project as the key, wait a few minutes, then rerun.

**My OAuth token stopped working after a week.** Apps left in "Testing" status get refresh tokens that expire after 7 days. Publish the consent screen, or use a service account.

**Is this AI-generated advice?** No. All text comes from fixed templates filled with your numbers, and the rules are documented above.

### Compliance and sources

- Data comes only from Google's official APIs with credentials you supply: Search Console API (searchanalytics.query, sitemaps.list, urlInspection.index.inspect) and Google Analytics Data API v1beta (runReport, runRealtimeReport). There is no scraping.
- The CTR benchmark is an approximation of public CTR studies and is labelled as such in every output.
- This is diagnostic analysis, **not legal advice** (including on consent/PECR/UK GDPR compliance) and not a guarantee of rankings or revenue.
- **Where results are stored:** the default dataset and key-value store of the run (the analysis rows, the raw API data per property as `data-…` records, HTML reports and `OUTPUT`), inside your own Apify account. These default storages are unnamed. How long Apify keeps them is set by Apify and your plan, not by SiteVerdict ([Apify data retention](https://docs.apify.com/platform/storage/usage#data-retention)); you can delete them sooner in Apify Console. The Actor keeps no named store.
- **Data licence:** your Search Console and GA4 data stays yours; it is fetched under Google's API terms with your own credentials.
- **What SiteVerdict keeps:** nothing. The Actor runs in your Apify account; data goes only between Google's APIs and your Apify storage, never to SiteVerdict.

Made by SiteVerdict (BharatFare Travel and Technologies Ltd, UK).

# Changelog

This Actor's version history is a separate document: https://apify.com/siteverdict/siteverdict-gsc-ga4-analyst/changelog.md

# Actor input Schema

## `authMethod` (type: `string`):

How the Actor reads YOUR data. 'Sample data' runs on a built-in fictional shop so you can see the report for free. 'Service account' (recommended): paste a Google Cloud service-account JSON key and add its email as a user in Search Console and GA4. 'OAuth': your own OAuth client + refresh token. Read-only scopes only; credentials are used in memory for the run and never written to the output.

## `serviceAccountJson` (type: `string`):

Paste the full JSON key file of a Google Cloud service account (Google Cloud console > IAM & Admin > Service accounts > Keys > Add key > JSON). Enable 'Google Search Console API' and 'Google Analytics Data API' in that project, then add the service account's client_email as a user in Search Console (Restricted is enough) and as a Viewer in GA4. Stored encrypted by Apify; never logged or output.

## `oauthClientId` (type: `string`):

Only for the OAuth method: client ID of your own OAuth client (Google Cloud console > APIs & Services > Credentials).

## `oauthClientSecret` (type: `string`):

Only for the OAuth method: the client secret of the same OAuth client.

## `oauthRefreshToken` (type: `string`):

Only for the OAuth method: a refresh token issued to that client with the scopes webmasters.readonly and analytics.readonly (e.g. from the Google OAuth 2.0 Playground with 'Use your own OAuth credentials'). Tokens from apps left in 'Testing' status expire after 7 days.

## `properties` (type: `array`):

One entry per site: {"label": "My shop", "gscSiteUrl": "sc-domain:example.com" or "https://www.example.com/", "ga4PropertyId": "123456789"}. Either field may be omitted. The GA4 Property ID is the number in GA4 Admin > Property details (not the G- measurement ID). Each entry is billed once as 'property-analysed'. Ignored in sample mode.

## `comparison` (type: `string`):

Previous period = the N days just before. Year over year = the same N days 52 weeks earlier (weekday-aligned; best for seasonal sites). Custom = your own dates below.

## `periodDays` (type: `integer`):

Length of the current period for previous-period and year-over-year comparisons. Search Console keeps 16 months of data.

## `dataLagDays` (type: `integer`):

Search Console data is usually final after 2-3 days, so the current period ends this many days before today.

## `currentStartDate` (type: `string`):

YYYY-MM-DD. Only used when 'Compare with' is Custom.

## `currentEndDate` (type: `string`):

YYYY-MM-DD, inclusive.

## `previousStartDate` (type: `string`):

YYYY-MM-DD. Leave empty to use the same number of days just before the current period.

## `previousEndDate` (type: `string`):

YYYY-MM-DD, inclusive.

## `searchType` (type: `string`):

Which Search Console report to analyse.

## `brandTerms` (type: `array`):

Words that identify brand searches (e.g. your company name and misspellings). Brand queries are excluded from opportunities and cannibalisation and reported separately.

## `minImpressions` (type: `integer`):

Queries/pages need at least this many impressions in the current period to be listed as an opportunity or cannibalisation case.

## `maxUrlInspections` (type: `integer`):

The top losing pages are checked with Google's URL Inspection API to confirm deindexing / canonical changes. Each inspection is billed as 'url-inspected'. Google allows 2,000 inspections per site per day. 0 = off.

## `includeGa4Realtime` (type: `boolean`):

Also calls runRealtimeReport (last 30 minutes) to confirm the GA4 tag is receiving data right now.

## `dataState` (type: `string`):

'final' uses finalised data only; 'all' includes fresh (still changing) data.

## `maxRowsPerQuery` (type: `integer`):

Cap on rows fetched per Search Console / GA4 report (pages, queries, landing pages). Search Console exposes at most 50,000 rows per day per search type; large sites can raise this.

## `maxTableRows` (type: `integer`):

How many rows each finding's evidence table shows in the dataset and HTML report (full tables are in the data-\* key-value record).

## `inspectionLanguage` (type: `string`):

BCP-47 language for coverage messages from the URL Inspection API.

## `ctrCurve` (type: `array`):

Optional list of 10-20 expected CTRs (0-1) for positions 1, 2, 3... Replaces the built-in benchmark curve for opportunity estimates.

## Actor input object example

```json
{
  "authMethod": "sample",
  "properties": [
    {
      "label": "My website",
      "gscSiteUrl": "sc-domain:example.com",
      "ga4PropertyId": "123456789"
    }
  ],
  "comparison": "previous_period",
  "periodDays": 28,
  "dataLagDays": 3,
  "searchType": "web",
  "brandTerms": [],
  "minImpressions": 100,
  "maxUrlInspections": 10,
  "includeGa4Realtime": true,
  "dataState": "final",
  "maxRowsPerQuery": 25000,
  "maxTableRows": 25,
  "inspectionLanguage": "en-GB"
}
```

# Actor output Schema

## `results` (type: `string`):

No description

## `report` (type: `string`):

No description

## `output` (type: `string`):

No description

## `files` (type: `string`):

No description

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "authMethod": "sample",
    "properties": [
        {
            "label": "My website",
            "gscSiteUrl": "sc-domain:example.com",
            "ga4PropertyId": "123456789"
        }
    ],
    "brandTerms": []
};

// Run the Actor and wait for it to finish
const run = await client.actor("siteverdict/siteverdict-gsc-ga4-analyst").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {
    "authMethod": "sample",
    "properties": [{
            "label": "My website",
            "gscSiteUrl": "sc-domain:example.com",
            "ga4PropertyId": "123456789",
        }],
    "brandTerms": [],
}

# Run the Actor and wait for it to finish
run = client.actor("siteverdict/siteverdict-gsc-ga4-analyst").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "authMethod": "sample",
  "properties": [
    {
      "label": "My website",
      "gscSiteUrl": "sc-domain:example.com",
      "ga4PropertyId": "123456789"
    }
  ],
  "brandTerms": []
}' |
apify call siteverdict/siteverdict-gsc-ga4-analyst --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,siteverdict/siteverdict-gsc-ga4-analyst"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/nmNBY888nVcu3iwFG/builds/eoYRcVpKcUcs6sxLj/openapi.json
