---
title: "Wikimedia REST API (en.wikipedia.org): spec vs live"
date: 2026-10-30
canonical: https://apidrifter.com/research/public-api-spec-drift-2026-11/wikipedia-rest/
part_of: https://apidrifter.com/research/public-api-spec-drift-2026-11/
---

# Wikimedia REST API (en.wikipedia.org): spec vs live

Part of [We checked 13 public APIs against their own OpenAPI specs. 9 had hard mismatches.](https://apidrifter.com/research/public-api-spec-drift-2026-11/).

## Summary

- Spec: https://en.wikipedia.org/api/rest_v1/?spec (OpenAPI 3)
- Base URL: `https://en.wikipedia.org/api/rest_v1`
- Run: 2026-10-30, read-only GET requests, no credentials
- Coverage: 49 operations in the spec, 19 requests sent, 13 successful, 19 operations observed
- Hard violations: 8 distinct schema locations
- Reviewer note: Confirmed: media-list items carry undocumented leadImage and srcset; /page/lint returns an array where the spec says object; 400 errors come back as application/json, documented as application/problem+json.

## Hard violations (8)

A typed client generated from the spec would break on these.

- `response-type-mismatch` (hard, breaking) at `GET /page/lint/{title} > response 200 > application/json`
  - the response body: expected "object" but got array
  - Request: `GET /page/lint/Earth -> 200 application/json`
  - Expected: `["object"]`
  - Actual: `"array"`
  - If the spec is the source of truth: Return "object" in the response body.
  - If the implementation is the source of truth: Change the type at paths["/page/lint/{title}"].get.responses["200"].content["application/json"].schema to "array".
- `response-type-mismatch` (hard, breaking) at `GET /page/lint/{title}/{revision} > response 200 > application/json`
  - the response body: expected "object" but got array
  - Request: `GET /page/lint/Earth/1 -> 200 application/json`
  - Expected: `["object"]`
  - Actual: `"array"`
  - If the spec is the source of truth: Return "object" in the response body.
  - If the implementation is the source of truth: Change the type at paths["/page/lint/{title}/{revision}"].get.responses["200"].content["application/json"].schema to "array".
- `response-type-mismatch` (hard, breaking) at `GET /page/mobile-html-offline-resources/{title} > response 200 > application/json`
  - the response body: expected "string" but got array
  - Request: `GET /page/mobile-html-offline-resources/Earth -> 200 application/json; charset=utf-8; profile="https://www.mediawiki.org/wiki/Specs/Mobile-HTML-Offline-Resources/1.2.1"`
  - Expected: `["string"]`
  - Actual: `"array"`
  - If the spec is the source of truth: Return "string" in the response body.
  - If the implementation is the source of truth: Change the type at paths["/page/mobile-html-offline-resources/{title}"].get.responses["200"].content["application/json"].schema to "array".
- `response-type-mismatch` (hard, breaking) at `GET /page/mobile-html-offline-resources/{title}/{revision} > response 200 > application/json`
  - the response body: expected "string" but got array
  - Request: `GET /page/mobile-html-offline-resources/Earth/1 -> 200 application/json; charset=utf-8; profile="https://www.mediawiki.org/wiki/Specs/Mobile-HTML-Offline-Resources/1.2.1"`
  - Expected: `["string"]`
  - Actual: `"array"`
  - If the spec is the source of truth: Return "string" in the response body.
  - If the implementation is the source of truth: Change the type at paths["/page/mobile-html-offline-resources/{title}/{revision}"].get.responses["200"].content["application/json"].schema to "array".
- `response-missing-required-property` (hard, breaking) at `GET /page/title/{title} > response 200 > application/json > /count`
  - response body at /count: required property "count" is missing
  - Request: `GET /page/title/Earth -> 200 application/json`
  - Expected: `"count"`
  - If the spec is the source of truth: Include "count" in the 200 response of GET /page/title/{title}.
  - If the implementation is the source of truth: Remove "count" from required at paths["/page/title/{title}"].get.responses["200"].content["application/json"].schema.
- `response-type-mismatch` (hard, breaking) at `GET /page/title/{title} > response 200 > application/json > /items`
  - response body at /items: expected "object" but got array
  - Request: `GET /page/title/Earth -> 200 application/json`
  - Expected: `["object"]`
  - Actual: `"array"`
  - If the spec is the source of truth: Return "object" in response body at /items.
  - If the implementation is the source of truth: Change the type at paths["/page/title/{title}"].get.responses["200"].content["application/json"].schema.properties["items"] to "array".
- `response-missing-required-property` (hard, breaking) at `GET /page/title/{title}/{revision} > response 200 > application/json > /count`
  - response body at /count: required property "count" is missing
  - Request: `GET /page/title/Earth/1 -> 200 application/json`
  - Expected: `"count"`
  - If the spec is the source of truth: Include "count" in the 200 response of GET /page/title/{title}/{revision}.
  - If the implementation is the source of truth: Remove "count" from required at paths["/page/title/{title}/{revision}"].get.responses["200"].content["application/json"].schema.
- `response-type-mismatch` (hard, breaking) at `GET /page/title/{title}/{revision} > response 200 > application/json > /items`
  - response body at /items: expected "object" but got array
  - Request: `GET /page/title/Earth/1 -> 200 application/json`
  - Expected: `["object"]`
  - Actual: `"array"`
  - If the spec is the source of truth: Return "object" in response body at /items.
  - If the implementation is the source of truth: Change the type at paths["/page/title/{title}/{revision}"].get.responses["200"].content["application/json"].schema.properties["items"] to "array".

## Other body findings (12)

Undocumented fields and loose constraints on successful responses. Documentation gaps, not violations.

- `response-undocumented-property` (body, info) at `GET /page/media-list/{title} > response 200 > application/json > /items/0/leadImage`
  - response body at /items/0/leadImage: property "leadImage" is not documented (and 34 more like it)
  - Request: `GET /page/media-list/Earth -> 200 application/json; charset=utf-8; profile="https://www.mediawiki.org/wiki/Specs/MediaList/1.1.0"`
  - Actual: `"leadImage"`
  - If the spec is the source of truth: Stop returning "leadImage" from GET /page/media-list/{title} if it is not part of the contract.
  - If the implementation is the source of truth: Add "leadImage" to paths["/page/media-list/{title}"].get.responses["200"].content["application/json"].schema.properties["items"].items.properties.
- `response-undocumented-property` (body, info) at `GET /page/media-list/{title} > response 200 > application/json > /items/0/srcset`
  - response body at /items/0/srcset: property "srcset" is not documented (and 30 more like it)
  - Request: `GET /page/media-list/Earth -> 200 application/json; charset=utf-8; profile="https://www.mediawiki.org/wiki/Specs/MediaList/1.1.0"`
  - Actual: `"srcset"`
  - If the spec is the source of truth: Stop returning "srcset" from GET /page/media-list/{title} if it is not part of the contract.
  - If the implementation is the source of truth: Add "srcset" to paths["/page/media-list/{title}"].get.responses["200"].content["application/json"].schema.properties["items"].items.properties.
- `response-undocumented-property` (body, info) at `GET /page/media-list/{title} > response 200 > application/json > /items/28/gallery_id`
  - response body at /items/28/gallery_id: property "gallery_id" is not documented (and 3 more like it)
  - Request: `GET /page/media-list/Earth -> 200 application/json; charset=utf-8; profile="https://www.mediawiki.org/wiki/Specs/MediaList/1.1.0"`
  - Actual: `"gallery_id"`
  - If the spec is the source of truth: Stop returning "gallery_id" from GET /page/media-list/{title} if it is not part of the contract.
  - If the implementation is the source of truth: Add "gallery_id" to paths["/page/media-list/{title}"].get.responses["200"].content["application/json"].schema.properties["items"].items.properties.
- `response-undocumented-property` (body, info) at `GET /page/media-list/{title} > response 200 > application/json > /items/31/sources`
  - response body at /items/31/sources: property "sources" is not documented
  - Request: `GET /page/media-list/Earth -> 200 application/json; charset=utf-8; profile="https://www.mediawiki.org/wiki/Specs/MediaList/1.1.0"`
  - Actual: `"sources"`
  - If the spec is the source of truth: Stop returning "sources" from GET /page/media-list/{title} if it is not part of the contract.
  - If the implementation is the source of truth: Add "sources" to paths["/page/media-list/{title}"].get.responses["200"].content["application/json"].schema.properties["items"].items.properties.
- `response-undocumented-property` (body, info) at `GET /page/media-list/{title} > response 200 > application/json > /items/34/audio_type`
  - response body at /items/34/audio_type: property "audio_type" is not documented
  - Request: `GET /page/media-list/Earth -> 200 application/json; charset=utf-8; profile="https://www.mediawiki.org/wiki/Specs/MediaList/1.1.0"`
  - Actual: `"audio_type"`
  - If the spec is the source of truth: Stop returning "audio_type" from GET /page/media-list/{title} if it is not part of the contract.
  - If the implementation is the source of truth: Add "audio_type" to paths["/page/media-list/{title}"].get.responses["200"].content["application/json"].schema.properties["items"].items.properties.
- `response-undocumented-property` (body, info) at `GET /page/summary/{title} > response 200 > application/json > /content_urls`
  - response body at /content_urls: property "content_urls" is not documented
  - Request: `GET /page/summary/Earth -> 200 application/json; charset=utf-8; profile="https://www.mediawiki.org/wiki/Specs/Summary/1.5.0"`
  - Actual: `"content_urls"`
  - If the spec is the source of truth: Stop returning "content_urls" from GET /page/summary/{title} if it is not part of the contract.
  - If the implementation is the source of truth: Add "content_urls" to paths["/page/summary/{title}"].get.responses["200"].content["application/json"].schema.properties.
- `response-undocumented-property` (body, info) at `GET /page/summary/{title} > response 200 > application/json > /description_source`
  - response body at /description_source: property "description_source" is not documented
  - Request: `GET /page/summary/Earth -> 200 application/json; charset=utf-8; profile="https://www.mediawiki.org/wiki/Specs/Summary/1.5.0"`
  - Actual: `"description_source"`
  - If the spec is the source of truth: Stop returning "description_source" from GET /page/summary/{title} if it is not part of the contract.
  - If the implementation is the source of truth: Add "description_source" to paths["/page/summary/{title}"].get.responses["200"].content["application/json"].schema.properties.
- `response-undocumented-property` (body, info) at `GET /page/summary/{title} > response 200 > application/json > /namespace`
  - response body at /namespace: property "namespace" is not documented
  - Request: `GET /page/summary/Earth -> 200 application/json; charset=utf-8; profile="https://www.mediawiki.org/wiki/Specs/Summary/1.5.0"`
  - Actual: `"namespace"`
  - If the spec is the source of truth: Stop returning "namespace" from GET /page/summary/{title} if it is not part of the contract.
  - If the implementation is the source of truth: Add "namespace" to paths["/page/summary/{title}"].get.responses["200"].content["application/json"].schema.properties.
- `response-undocumented-property` (body, info) at `GET /page/summary/{title} > response 200 > application/json > /revision`
  - response body at /revision: property "revision" is not documented
  - Request: `GET /page/summary/Earth -> 200 application/json; charset=utf-8; profile="https://www.mediawiki.org/wiki/Specs/Summary/1.5.0"`
  - Actual: `"revision"`
  - If the spec is the source of truth: Stop returning "revision" from GET /page/summary/{title} if it is not part of the contract.
  - If the implementation is the source of truth: Add "revision" to paths["/page/summary/{title}"].get.responses["200"].content["application/json"].schema.properties.
- `response-undocumented-property` (body, info) at `GET /page/summary/{title} > response 200 > application/json > /tid`
  - response body at /tid: property "tid" is not documented
  - Request: `GET /page/summary/Earth -> 200 application/json; charset=utf-8; profile="https://www.mediawiki.org/wiki/Specs/Summary/1.5.0"`
  - Actual: `"tid"`
  - If the spec is the source of truth: Stop returning "tid" from GET /page/summary/{title} if it is not part of the contract.
  - If the implementation is the source of truth: Add "tid" to paths["/page/summary/{title}"].get.responses["200"].content["application/json"].schema.properties.
- `response-undocumented-property` (body, info) at `GET /page/summary/{title} > response 200 > application/json > /type`
  - response body at /type: property "type" is not documented
  - Request: `GET /page/summary/Earth -> 200 application/json; charset=utf-8; profile="https://www.mediawiki.org/wiki/Specs/Summary/1.5.0"`
  - Actual: `"type"`
  - If the spec is the source of truth: Stop returning "type" from GET /page/summary/{title} if it is not part of the contract.
  - If the implementation is the source of truth: Add "type" to paths["/page/summary/{title}"].get.responses["200"].content["application/json"].schema.properties.
- `response-undocumented-property` (body, info) at `GET /page/summary/{title} > response 200 > application/json > /wikibase_item`
  - response body at /wikibase_item: property "wikibase_item" is not documented
  - Request: `GET /page/summary/Earth -> 200 application/json; charset=utf-8; profile="https://www.mediawiki.org/wiki/Specs/Summary/1.5.0"`
  - Actual: `"wikibase_item"`
  - If the spec is the source of truth: Stop returning "wikibase_item" from GET /page/summary/{title} if it is not part of the contract.
  - If the implementation is the source of truth: Add "wikibase_item" to paths["/page/summary/{title}"].get.responses["200"].content["application/json"].schema.properties.

## Undocumented error responses (9)

3xx and 4xx statuses the spec does not mention, usually auth or not-found cases.

- `request-rejected-but-valid` (errors, breaking) at `GET /data/lists/`
  - request to "GET /data/lists/" conformed to the spec but was rejected with 400
  - Request: `GET /data/lists/?sort=updated -> 400 application/json`
  - Actual: `400`
  - If the spec is the source of truth: Accept requests to GET /data/lists/ that conform to the spec; this one was rejected with 400.
  - If the implementation is the source of truth: Document the validation GET /data/lists/ performs (required fields, formats, constraints) so the spec reflects what is accepted.
- `undocumented-response-media-type` (errors, warning) at `GET /data/lists/ > response 400 > application/json`
  - media type "application/json" is not documented for the 400 response
  - Request: `GET /data/lists/?sort=updated -> 400 application/json`
  - Expected: `["application/problem+json"]`
  - Actual: `"application/json"`
  - If the spec is the source of truth: Return one of application/problem+json from GET /data/lists/.
  - If the implementation is the source of truth: Add "application/json" under paths["/data/lists/"].get.responses["default"].content.
- `request-rejected-but-valid` (errors, breaking) at `GET /data/lists/pages/{project}/{title}`
  - request to "GET /data/lists/pages/{project}/{title}" conformed to the spec but was rejected with 400
  - Request: `GET /data/lists/pages/https%3A%2F%2Fen.wikipedia.org/Earth -> 400 application/json`
  - Actual: `400`
  - If the spec is the source of truth: Accept requests to GET /data/lists/pages/{project}/{title} that conform to the spec; this one was rejected with 400.
  - If the implementation is the source of truth: Document the validation GET /data/lists/pages/{project}/{title} performs (required fields, formats, constraints) so the spec reflects what is accepted.
- `undocumented-response-media-type` (errors, warning) at `GET /data/lists/pages/{project}/{title} > response 400 > application/json`
  - media type "application/json" is not documented for the 400 response
  - Request: `GET /data/lists/pages/https%3A%2F%2Fen.wikipedia.org/Earth -> 400 application/json`
  - Expected: `["application/problem+json"]`
  - Actual: `"application/json"`
  - If the spec is the source of truth: Return one of application/problem+json from GET /data/lists/pages/{project}/{title}.
  - If the implementation is the source of truth: Add "application/json" under paths["/data/lists/pages/{project}/{title}"].get.responses["default"].content.
- `request-rejected-but-valid` (errors, breaking) at `GET /data/lists/{id}/entries/`
  - request to "GET /data/lists/{id}/entries/" conformed to the spec but was rejected with 400
  - Request: `GET /data/lists/42/entries/?sort=updated -> 400 application/json`
  - Actual: `400`
  - If the spec is the source of truth: Accept requests to GET /data/lists/{id}/entries/ that conform to the spec; this one was rejected with 400.
  - If the implementation is the source of truth: Document the validation GET /data/lists/{id}/entries/ performs (required fields, formats, constraints) so the spec reflects what is accepted.
- `undocumented-response-media-type` (errors, warning) at `GET /data/lists/{id}/entries/ > response 400 > application/json`
  - media type "application/json" is not documented for the 400 response
  - Request: `GET /data/lists/42/entries/?sort=updated -> 400 application/json`
  - Expected: `["application/problem+json"]`
  - Actual: `"application/json"`
  - If the spec is the source of truth: Return one of application/problem+json from GET /data/lists/{id}/entries/.
  - If the implementation is the source of truth: Add "application/json" under paths["/data/lists/{id}/entries/"].get.responses["default"].content.
- `undocumented-response-media-type` (errors, warning) at `GET /page/ > response 404 > application/json`
  - media type "application/json" is not documented for the 404 response
  - Request: `GET /page/ -> 404 application/json`
  - Expected: `["application/problem+json"]`
  - Actual: `"application/json"`
  - If the spec is the source of truth: Return one of application/problem+json from GET /page/.
  - If the implementation is the source of truth: Add "application/json" under paths["/page/"].get.responses["default"].content.
- `undocumented-response-media-type` (errors, warning) at `GET /page/talk/{title} > response 403 > text/html`
  - media type "text/html" is not documented for the 403 response
  - Request: `GET /page/talk/Earth -> 403 text/html; charset=utf-8`
  - Expected: `["application/problem+json"]`
  - Actual: `"text/html"`
  - If the spec is the source of truth: Return one of application/problem+json from GET /page/talk/{title}.
  - If the implementation is the source of truth: Add "text/html" under paths["/page/talk/{title}"].get.responses["default"].content.
- `undocumented-response-media-type` (errors, warning) at `GET /page/talk/{title}/{revision} > response 403 > text/html`
  - media type "text/html" is not documented for the 403 response
  - Request: `GET /page/talk/Earth/1 -> 403 text/html; charset=utf-8`
  - Expected: `["application/problem+json"]`
  - Actual: `"text/html"`
  - If the spec is the source of truth: Return one of application/problem+json from GET /page/talk/{title}/{revision}.
  - If the implementation is the source of truth: Add "text/html" under paths["/page/talk/{title}/{revision}"].get.responses["default"].content.

## Reproduce

```sh
curl -s -H 'Accept: application/json' 'https://en.wikipedia.org/api/rest_v1/page/lint/Earth'
```

## Data

- Findings: https://github.com/apidrifter/apidrifter/tree/main/tools/survey/results/wikipedia-rest.findings.json
- Requests sent: https://github.com/apidrifter/apidrifter/tree/main/tools/survey/results/wikipedia-rest.observations.json
- Tool: https://apidrifter.com/docs/cli/ (reproduce with apidrifter run; target definition in tools/survey/targets.json)
