# OpenAPI Publish Readiness and Example Conformance Gate (`kingii98/openapi-publish-readiness-and-example-conformance-gate`) Actor

Audit the OpenAPI or Swagger documents that you publish. The Actor reports every unresolved reference, every duplicate or absent operationId, every response without a schema, and every example that violates its own schema, with a JSON pointer and a sugges

- **URL**: https://apify.com/kingii98/openapi-publish-readiness-and-example-conformance-gate.md
- **Developed by:** [kingii98](https://apify.com/kingii98) (community)
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $10.00 / 1,000 run\_starts

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?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
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.
Actors are written with capital "A".

## 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.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## OpenAPI Publish Readiness and Example Conformance Gate

Audit the OpenAPI 3.x or Swagger 2.0 documents that you publish, before a partner
reads them. The Actor downloads each document, resolves its references, validates
every declared example against its own schema, and reports each defect with a JSON
pointer, a rule ID, a severity and a suggested fix.

The Actor answers one question for the API platform team and for the developer
relations engineer: **can a mock tool, an SDK generator or a partner test suite use
this specification without manual repair?**

The Actor reads documents only. It sends no credential, it calls no protected
endpoint, and it uses no browser and no proxy. The one write-free probe that it
sends outside the documents is an unauthenticated `HEAD` against each declared
server URL, and you can turn that probe off.

### What the Actor checks

| Rule ID | What it finds |
| --- | --- |
| `OAS-PARSE-FAILED` | The document could not be downloaded, or it is neither JSON nor YAML. |
| `OAS-VERSION-UNKNOWN` | The document declares no OpenAPI 3.x version and no Swagger 2.0 version. |
| `OAS-NO-OPERATIONS` | The document declares no path with an HTTP method. |
| `OAS-OPERATIONS-TRUNCATED` | The document holds more operations than `maxOperationsPerSpec`. |
| `OAS-REF-UNRESOLVED` | A `$ref` names nothing in the document. A generator stops here. |
| `OAS-REF-EXTERNAL` | A `$ref` points outside the document, so the document is not self-contained. |
| `OAS-REF-CIRCULAR` | Named schemas take part in a reference cycle. |
| `OAS-OPERATION-ID-MISSING` | An operation declares no `operationId`, so a client method has no name. |
| `OAS-OPERATION-ID-DUPLICATE` | Two operations share one `operationId`. |
| `OAS-RESPONSES-MISSING` | An operation declares no response at all. |
| `OAS-RESPONSE-NO-SCHEMA` | A response that carries a body declares no schema. A mock server cannot invent one. |
| `OAS-EXAMPLE-INVALID` | A declared example violates the schema next to it. |
| `OAS-EXAMPLE-MISSING` | A success response declares a schema and no example. |
| `OAS-PAGINATION-MISSING` | A `GET` that answers with a collection declares no pagination parameter. |
| `OAS-SECURITY-SCHEMES-MISSING` | The document declares no security scheme. |
| `OAS-OPERATION-NO-SECURITY` | An operation has no security requirement, and there is no global one. |
| `OAS-SERVERS-MISSING` | The document declares no server URL. |
| `OAS-SERVER-NOT-ABSOLUTE` | A declared server URL is not an absolute `http` or `https` URL. |
| `OAS-SERVER-UNREACHABLE` | A declared server URL did not answer the unauthenticated `HEAD`. |

An example is checked against the keywords that an OpenAPI schema uses for a body:
`$ref`, `type`, `nullable`, `enum`, `const`, `required`, `properties`,
`additionalProperties`, `items`, `minItems`, `maxItems`, `minimum`, `maximum`,
`exclusiveMinimum`, `exclusiveMaximum`, `multipleOf`, `minLength`, `maxLength`,
`pattern`, `allOf`, `anyOf`, `oneOf` and `not`. A keyword that the check does not
know stays silent, so the Actor never invents a defect.

### Profiles

A profile does not change what the Actor looks for. It changes the **severity** of
each rule, and so it changes the gate result.

| Profile | The question it asks |
| --- | --- |
| `mock-readiness` | Can a mock server answer every operation with a body? A response without a schema and an example that violates its schema are errors. |
| `sdk-generation` | Can a code generator name every operation and every type? An absent or duplicate `operationId`, an external reference and a reference cycle are errors. |
| `partner-publication` | Both questions, and also: can a partner find the service and does the partner know how to authenticate? This is the default. |

The gate result of one document is `FAIL` when the document holds at least one
defect that the profile calls an **error**, and `PASS` otherwise. A `FAIL` is a
business verdict: the run reports it in the dataset and in the status message, and
the run itself **succeeds**.

### Input

| Field | Type | Default | Meaning |
| --- | --- | --- | --- |
| `specUrls` | array of 1 to 50 URLs | the two public demo documents | The OpenAPI 3.x or Swagger 2.0 documents to audit. JSON and YAML are both read. |
| `profile` | `mock-readiness`, `sdk-generation` or `partner-publication` | `partner-publication` | Which question the gate asks. |
| `checkServerReachability` | boolean | `true` | Send one unauthenticated `HEAD` to each declared server URL. |
| `maxOperationsPerSpec` | integer 1 to 5000 | `1000` | How many operations of one document are validated. |
| `maxDefectsPerSpec` | integer 1 to 5000 | `500` | How many defect rows one document writes. |
| `timeoutSecs` | integer 2 to 120 | `30` | Per-request timeout. |
| `maxRedirects` | integer 0 to 20 | `5` | Redirect hops for each request. Every hop is checked again. |
| `maxResponseBytes` | integer 1000 to 20000000 | `20000000` | Per-document body cap. |

Every field has a schema default, so a run with the empty input `{}` audits the two
public demo documents and succeeds.

#### Safety limits

- Only `http` and `https` URLs. No credentials in a URL.
- `localhost`, and any hostname that resolves to a private or reserved address, is
  refused. Every redirect hop is resolved and checked again.
- At most 50 documents, at most 20 MB for each document, at most 10 declared server
  URLs probed for each document, and at most 5 requests in flight at one time.
- YAML aliases are refused, so an alias bomb cannot expand.
- The reference walk, the schema walk and the example check are all depth-capped
  and node-capped. The check of one example may visit 5000 schema nodes, so a nest
  of `anyOf`, `oneOf` or `not` members cannot multiply the work without an end.
- A `pattern` is matched under a time limit, and all matches of one document share
  a five-second allowance. A value longer than 4000 characters is not matched. A
  match that reaches a limit reports nothing, because a pattern that the Actor
  cannot evaluate is not proof of a bad example.

### Output

The dataset holds one gate summary row, one row for each document, and one row for
each defect. The `recordType` field separates them.

**Summary row** (`recordType: "summary"`): `profile`, `specsRequested`,
`specsAudited`, `specsUnreadable`, `specsFailingGate`, `specsMockReady`,
`specsSdkReady`, `operationsTotal`, `operationsValidated`, `examplesTotal`,
`examplesThatViolateSchema`, `defectsTotal`, `errorDefects`, `warningDefects`,
`gateResult`, `reasonCode`, `error`, `auditedAt`.

**Document row** (`recordType: "spec"`): `specUrl`, `specVersion`, `parseOk`,
`operationsTotal`, `unresolvedRefs`, `circularRefs`, `duplicateOperationIds`,
`missingOperationIds`, `examplesTotal`, `examplesThatViolateSchema`,
`responsesWithoutSchema`, `listOperationsWithoutPaginationParameters`,
`securitySchemesDeclared`, `operationsWithoutSecurity`, `servers[]` (each with
`url`, `headStatus`, `reachable`), `mockReady`, `sdkReady`, `gateResult`. The row
also carries `externalRefs`, `operationsValidated`, `operationsTruncated`,
`serversDeclared`, `serversReachable`, `defectsTotal`, `errorDefects`,
`warningDefects`, `defectsDropped`, `reasonCode`, `httpStatus`, `bytesRead`,
`responseTimeMs` and `error`.

**Defect row** (`recordType: "defect"`): `specUrl`, `jsonPointer`, `ruleId`,
`severity`, `message`, `suggestedFix`.

`mockReady` is true when the document parses, holds no unresolved reference, leaves
no body response without a schema, holds no example that violates its schema,
declares an absolute server URL, and (when the probe is on) has at least one server
that answers.

`sdkReady` is true when the document parses, holds at least one operation, and
holds no unresolved reference, no external reference, no reference cycle, no
duplicate `operationId` and no absent `operationId`.

### Pricing: pay per event

| Event | Unit | Counted as |
| --- | --- | --- |
| `run_start` | one for each run | Charged once, before any document work. It pays for the container start. |
| `spec_audited` | one for each OpenAPI document downloaded, resolved and validated | Charged once for each document that parsed. A document that could not be downloaded or could not be parsed was never resolved and never validated, so it is **not** charged. |
| `operation_batch_validated` | one for each batch of 50 operations above the first 50 | For each audited document: `ceil((validated operations - 50) / 50)`, and 0 when the document holds 50 operations or fewer. |

Every charge stays inside the run's maximum total charge. When the limit stops a
charge, the Actor writes a warning to the log, and every row still reaches the
dataset.

### Repeat use

Run the gate in the release pipeline at each specification version, and again
before each partner onboarding. The Actor keeps no state between runs: each run
validates one version of each document against fixed rules.

### Local development

```bash
uv sync
uv run pytest
uv run ruff check .
```

# Actor input Schema

## `specUrls` (type: `array`):

Public URLs of 1 to 50 OpenAPI 3.x or Swagger 2.0 documents. JSON and YAML are both read. The Actor reads the documents only. It calls no protected endpoint, it sends no credential, and it uses no browser and no proxy. Leave the list empty to audit the two public example documents shown below.

## `profile` (type: `string`):

Which question the gate asks. 'mock-readiness' asks whether a mock server can answer every operation with a body. 'sdk-generation' asks whether a code generator can name every operation and every type. 'partner-publication' asks both questions, and it also asks whether a partner can find the service and knows how to authenticate. The profile changes the severity of each rule, and so it changes the gate result.

## `checkServerReachability` (type: `boolean`):

Send one unauthenticated HEAD request to each declared server URL. A base URL that answers any status below 500 is reachable. Turn this off to keep the run inside the documents.

## `maxOperationsPerSpec` (type: `integer`):

How many operations of one document are validated. A larger document reports operationsTruncated, and its operationsTotal stays complete.

## `maxDefectsPerSpec` (type: `integer`):

How many defect rows one document writes. The counters of the document row stay complete, and defectsDropped reports what the cap removed.

## `timeoutSecs` (type: `integer`):

Per-request timeout for each document GET and each server HEAD. A request that exceeds it gets the TIMEOUT reason code.

## `maxRedirects` (type: `integer`):

Maximum redirect hops followed for each request. Every hop is checked again against the private-address rules.

## `maxResponseBytes` (type: `integer`):

Per-document body cap. A larger document gets the DOCUMENT\_TOO\_LARGE reason code and is not parsed.

## Actor input object example

```json
{
  "specUrls": [
    "https://raw.githubusercontent.com/OAI/OpenAPI-Specification/e77481c73e1c703c7de2e4249047b42b8e9b56f4/examples/v3.0/petstore.json",
    "https://raw.githubusercontent.com/OAI/OpenAPI-Specification/e77481c73e1c703c7de2e4249047b42b8e9b56f4/examples/v3.0/api-with-examples.json"
  ],
  "profile": "partner-publication",
  "checkServerReachability": true,
  "maxOperationsPerSpec": 1000,
  "maxDefectsPerSpec": 500,
  "timeoutSecs": 30,
  "maxRedirects": 5,
  "maxResponseBytes": 20000000
}
```

# Actor output Schema

## `dataset` (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 = {
    "specUrls": [
        "https://raw.githubusercontent.com/OAI/OpenAPI-Specification/e77481c73e1c703c7de2e4249047b42b8e9b56f4/examples/v3.0/petstore.json",
        "https://raw.githubusercontent.com/OAI/OpenAPI-Specification/e77481c73e1c703c7de2e4249047b42b8e9b56f4/examples/v3.0/api-with-examples.json"
    ],
    "profile": "partner-publication",
    "checkServerReachability": true,
    "maxOperationsPerSpec": 1000,
    "maxDefectsPerSpec": 500,
    "timeoutSecs": 30,
    "maxRedirects": 5,
    "maxResponseBytes": 20000000
};

// Run the Actor and wait for it to finish
const run = await client.actor("kingii98/openapi-publish-readiness-and-example-conformance-gate").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 = {
    "specUrls": [
        "https://raw.githubusercontent.com/OAI/OpenAPI-Specification/e77481c73e1c703c7de2e4249047b42b8e9b56f4/examples/v3.0/petstore.json",
        "https://raw.githubusercontent.com/OAI/OpenAPI-Specification/e77481c73e1c703c7de2e4249047b42b8e9b56f4/examples/v3.0/api-with-examples.json",
    ],
    "profile": "partner-publication",
    "checkServerReachability": True,
    "maxOperationsPerSpec": 1000,
    "maxDefectsPerSpec": 500,
    "timeoutSecs": 30,
    "maxRedirects": 5,
    "maxResponseBytes": 20000000,
}

# Run the Actor and wait for it to finish
run = client.actor("kingii98/openapi-publish-readiness-and-example-conformance-gate").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 '{
  "specUrls": [
    "https://raw.githubusercontent.com/OAI/OpenAPI-Specification/e77481c73e1c703c7de2e4249047b42b8e9b56f4/examples/v3.0/petstore.json",
    "https://raw.githubusercontent.com/OAI/OpenAPI-Specification/e77481c73e1c703c7de2e4249047b42b8e9b56f4/examples/v3.0/api-with-examples.json"
  ],
  "profile": "partner-publication",
  "checkServerReachability": true,
  "maxOperationsPerSpec": 1000,
  "maxDefectsPerSpec": 500,
  "timeoutSecs": 30,
  "maxRedirects": 5,
  "maxResponseBytes": 20000000
}' |
apify call kingii98/openapi-publish-readiness-and-example-conformance-gate --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,kingii98/openapi-publish-readiness-and-example-conformance-gate"
        }
    }
}
```

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/4mIwBiNGyiXrcjyvf/builds/pGwnPccjD0TzrOgfx/openapi.json
