# API contract drift watch — a replayable Reelier skill

Schema-shape contract check of a public JSON API (PyPI's pip metadata) — required fields present + value types, read-only

A real, read-only Reelier skill against a live public API — no account, no keys, no writes. Every replay proves: A public JSON API still answers with its documented schema — required fields present and correctly typed..

- Steps: 1 · Assertions: 7 · Effect: read (counts parsed from the file, never hand-typed)
- Endpoints: https://pypi.org/pypi/pip/json
- Canonical file: https://raw.githubusercontent.com/seldonframe/reelier/main/examples/portfolio/api-contract-drift-watch.skill.md
- HTML page: https://www.reelier.com/skills/api-contract-drift-watch

## What this checks

One step: an HTTP GET to the PyPI JSON metadata API for the pip package. Seven assertions pin the contract's shape across two nesting levels — the API answered 200; info.name, info.version and info.package_url are strings; urls is an array; releases is an object (is set); and last_serial is a number.

This is a shape check, never a value check. Presence and type are asserted; the live version, file list and serial are bound onto the receipt (name, version, last_serial) rather than pinned — so a routine new release changes the bound values without breaking the replay, while a dropped or retyped field, the exact breaking change every pip client would feel, fails it.

Honesty note: this hits the unauthenticated PyPI JSON API (no key). If the schema drifts, the matching is <type> assert fails and the receipt records a real failure — it never silently passes. That is the intended behavior, not a flake to paper over.

## The skill file (verbatim)

```markdown
---
name: api-contract-drift-watch
description: Schema-shape contract check of a public JSON API (PyPI's pip metadata) — required fields present + value types, read-only
---

<!-- synced from seldonframe/reelier examples/portfolio — edit there -->

# API contract drift watch

Inputs: (none — this file replays green as-is; see the personalization note)

<!--
  Personalization: point this at YOUR production API. Swap the URL for your
  own JSON endpoint and rewrite the `json.<path> is <type>` asserts to the
  required fields and types of YOUR contract (the portfolio README's "Point
  these at YOUR project" section has the one-command version for the simplest
  case — a different PyPI package). The skill grammar has no default-value
  syntax for {{var}} holes — an unbound {{var}} is an explicit error, never a
  guessed fallback — so this file ships with literals that replay green with
  zero flags.

  Honesty note: this hits the UNAUTHENTICATED PyPI JSON API (no key, no
  rate-limit gate for a single read). If PyPI ever drops or retypes one of
  these fields — the exact breaking change every pip client would feel — the
  matching `is <type>` assert fails and the receipt records a real failure. It
  never silently passes. That is the whole point, not a flake to paper over.

  Endpoint verified live on 2026-07-21: 200; info.name "pip",
  info.version "26.1.2", urls is array (2), releases is object, last_serial a number.
-->

This is a **shape** check, not a value check: the assertions pin the contract
— which fields must exist and what type each must be, across two nesting
levels (`info.*`, top-level `urls`/`releases`/`last_serial`) — never a
specific version string that would rot on the next release. A breaking change
to the API's shape fails the replay; a routine new release does not.

## Steps

### Step 1 — Assert the PyPI package-metadata contract holds
- intent: Fetch the PyPI JSON metadata for pip and assert the schema shape — required fields present with the right types
- action: http.get {"url": "https://pypi.org/pypi/pip/json"}
- assert: status == 200
- assert: json.info.name is string
- assert: json.info.version is string
- assert: json.info.package_url is string
- assert: json.urls is array
- assert: json.releases is set
- assert: json.last_serial is number
- bind: name = json.info.name
- bind: version = json.info.version
- bind: last_serial = json.last_serial
- effect: read
```

## Run it yourself

`reelier run` takes a local file, so fetch the canonical copy first (or clone the repo). The replay writes its receipt to `.reelier/runs/`.

```sh
curl -fsSL https://raw.githubusercontent.com/seldonframe/reelier/main/examples/portfolio/api-contract-drift-watch.skill.md -o api-contract-drift-watch.skill.md
npx -y reelier@latest run api-contract-drift-watch.skill.md

# or from a clone:
git clone https://github.com/seldonframe/reelier
cd reelier && npx -y reelier@latest run examples/portfolio/api-contract-drift-watch.skill.md
```

## Seed receipt — 2026-07-21

Replayed for real with the published CLI (resolved to reelier@0.14.0) on 2026-07-21; the run record confirms passed: true and 0 LLM tokens (input + output). Verbatim output:

```
✓ Step 1 — Assert the PyPI package-metadata contract holds [passed] 192ms

PASSED: 1/1 steps ok, 0 failed, 192ms total
```

Standing audit log: a GitHub Action replays all five portfolio skills every 6 hours (plus on demand) — https://github.com/seldonframe/reelier/actions/workflows/portfolio-replay.yml. A failing endpoint produces an honestly failing receipt, never a retried-until-green one.

## Point it at your project

Point it at YOUR production API: swap the URL for your own JSON endpoint and rewrite the json.<path> is <type> asserts to your contract's required fields and types. The simplest swap — a different PyPI package — is the one-liner below. The skill grammar has no default-value syntax for {{var}} holes — an unbound {{var}} is an explicit error, never a guessed fallback — so this file ships with literals that replay green with zero flags.

```sh
sed 's#pypi.org/pypi/pip/json#pypi.org/pypi/YOUR-PACKAGE/json#' api-contract-drift-watch.skill.md > my-contract-watch.skill.md \
  && npx -y reelier@latest run my-contract-watch.skill.md
```

---

Reelier — agents make claims, Reelier writes receipts. AGPL-3.0.
Portfolio index: https://www.reelier.com/skills (twin: https://www.reelier.com/skills.md) · Replay ledger: https://www.reelier.com/replays · Machine-readable summary: https://www.reelier.com/llms.txt
