# Contribution API acceptance workflow — version 1.0

By George Kelly · October 4, 2026

Canonical guide: https://www.iamgeorgekelly.com/field-guide/contribution-api-acceptance-workflow

This dependency-free Node.js workflow checks an implemented contribution API with frozen synthetic inputs. The retained run used **Node.js v22.20.0** against `https://www.iamgeorgekelly.com`, from **2026-10-04T14:22:52.767Z** to **2026-10-04T14:22:54.266Z**. All **14 HTTP expectations** matched: eight HTTP 200 arithmetic scenarios and six expected HTTP 400 rejections. All **five caller checks** matched, including four holds for missing/conflicting currency or cohort labels.

These are internal acceptance observations. They measure the supplied cases and client, with no language-model calls, independent users or business records.

## Files and setup

Save these four files in one directory, retaining their names:

- `contribution-api-acceptance.mjs` — runner and reusable caller/response functions.
- `contribution-api-acceptance-cases.json` — exact requests, rational/decimal expectations and caller metadata.
- `contribution-api-acceptance-source-snapshot.json` — reviewed local formula, HTTP route and OpenAPI route, with source hashes.
- `contribution-api-acceptance-results.json` — complete retained run, HTTP responses, caller findings and provenance hashes.

Use **Node.js 22 or later**. There are no third-party dependencies to install.

## Verify the retained evidence offline

```sh
node contribution-api-acceptance.mjs --check
```

Running without arguments also uses offline mode. This verifies all saved expectations, request URLs, complete case inventories, caller decisions, summary counts and hashes. It sends **zero network requests**. A successful check reproduces acceptance of the saved observations; it does not check the current service.

## Execute the fixed panel

```sh
node contribution-api-acceptance.mjs --run > contribution-api-acceptance-rerun.json
```

For a built local endpoint:

```sh
node contribution-api-acceptance.mjs --run --base http://localhost:3000 > contribution-api-acceptance-local.json
```

Remote origins require HTTPS. A base must be an origin without a path, query, fragment or credentials; HTTP is permitted only on localhost. One run sends **15 sequential GET requests**: the OpenAPI description plus the 14 case requests. Each has a 10-second timeout, one attempt and no retry. The five caller checks send zero requests. Amounts are the frozen fictional examples. Preserve the original file and save a rerun under a different filename.

The runner uses `User-Agent: iamgeorgekelly-ContentQA-contribution-api-acceptance/1.0`. Treat these as internal QA when reviewing request logs. They establish no independent integration, audience use or traffic result. The result records no performance benchmark.

The process exits with a failure status if the contract or any case misses its expectation. An expected 400 is an accepted negative case; a timeout or unexpected error is a failure. Inspect every case rather than relying only on the summary.

## A complete client example

Save this as `contribution-client.mjs` beside the four downloaded files, then run `node contribution-client.mjs`. This example makes **one additional synthetic API request**, separate from the fixed acceptance panel.

```javascript
import { readFileSync } from 'node:fs';
import {
  prepareScenario,
  verifyResponse,
  userAgent,
} from './contribution-api-acceptance.mjs';

const corpus = JSON.parse(readFileSync(
  new URL('./contribution-api-acceptance-cases.json', import.meta.url),
  'utf8',
));
const scenario = corpus.callerCases.find(c => c.id === 'matching-labels-ready');
const expectation = corpus.httpCases.find(c => c.id === 'positive-control');
const prepared = prepareScenario(scenario.records);

if (prepared.status !== 'ready') {
  throw new Error(`Calculation held: ${prepared.failures.join(' ')}`);
}
const url = `https://www.iamgeorgekelly.com/api/tools/contribution?${prepared.request}`;
const response = await fetch(url, {
  method: 'GET',
  redirect: 'error',
  signal: AbortSignal.timeout(10000),
  headers: { Accept: 'application/json', 'User-Agent': userAgent },
});
const headers = Object.fromEntries([
  'content-type', 'cache-control', 'access-control-allow-origin',
  'x-robots-tag', 'link',
].map(name => [name, response.headers.get(name)]));
const body = await response.json();
const failures = verifyResponse(expectation, {
  status: response.status,
  headers,
  body,
  networkError: null,
});
if (failures.length) throw new Error(failures.join('\n'));

console.log(JSON.stringify({
  currency: prepared.currency,
  cohortId: prepared.cohortId,
  source: 'Frozen fictional example; internal client QA.',
  rawResult: body,
}, null, 2));
```

Expected selected result: currency `USD`; cohort `fictional-orders-2026-09-settled-v1`; formula version `1.0`; contribution after ads `100`; revenue ROAS `5`; pre-ad contribution margin `0.3`. The raw scope statement says that the scenario excludes fixed overhead and does not establish incrementality.

The example's expectation is specific to the frozen positive control. When adapting a client to different amounts, supply independently reviewed expectations appropriate to those amounts. Preserve currency/cohort source evidence beside the result; those labels are absent from the API contract and response.

## What the caller gate establishes

`prepareScenario(records)` expects exactly `netRevenue`, `costOfGoods`, `variableCosts` and `adSpend`. Each is an object with a decimal-string `amount`, an uppercase three-letter `currency` label and a nonempty `cohortId`. It checks amount syntax/bounds and label consistency. Its `hold` result contains reasons and `request: null`.

The gate does not verify that a three-letter label is an assigned currency code, validate an exchange rate, convert currencies, authenticate a settlement amount or prove that a cohort label is accurate. A shared label is an explicit caller assumption. Unknown or mismatched labels stay held until reconciled in the source records. Sending `currency=USD` to the endpoint is an unknown-parameter error.

## Acceptance semantics

- Preserve negative contribution outputs. Negative input amounts are rejected.
- Zero spend gives `null` for the three spend-return ratios. Zero revenue with positive spend gives revenue ROAS `0` and a `null` revenue-based margin.
- Break-even ROAS is `null` when revenue or contribution before ads is non-positive.
- All inputs are required exactly once. A missing amount is never defaulted to zero; a repeated identical amount is still rejected.
- Each input is at most `1e12`; an output can have a larger absolute magnitude.
- The JSON response uses unrounded JavaScript double precision. Expected numbers are decimal or rational strings; numeric comparison uses eight scaled machine epsilons and exact null checks.
- Required result fields, returned inputs, formula version, units/scope statements and selected response headers are checked.

## Provenance and limits

The source snapshot binds the three reviewed local files to their bytes and Git revision. It does **not** independently identify the source code deployed behind the public endpoint. The complete observed contract and responses establish the externally observed behavior at the recorded time. The saved result also hashes the runner, fixtures, source snapshot and parsed contract response.

The selected 14 cases are an authored acceptance panel. They do not establish exhaustive correctness, load tolerance, uptime, client-wide compatibility, security, real accounting accuracy, profit after overhead, causal advertising impact, model performance, tool adoption or business gain. Recheck the contract and retain a new complete run when the client, formula or expectations materially change.

## Primary references

- [Implemented OpenAPI contract](https://www.iamgeorgekelly.com/openapi.json) — observed HTTP 200 JSON in the retained run.
- [Official Node.js v22.20.0 globals documentation](https://raw.githubusercontent.com/nodejs/node/v22.20.0/doc/api/globals.md) — `fetch` and `AbortSignal.timeout`.
- [Official Node.js v22.20.0 URL documentation](https://raw.githubusercontent.com/nodejs/node/v22.20.0/doc/api/url.md) — query construction and duplicate-key semantics.
- [OpenAPI 3.1.1 specification](https://spec.openapis.org/oas/v3.1.1.html) — interface-description role. This workflow's selected checks are not an OpenAPI certification.
