# BLS Observation Revision Ledger (`flintglade/bls-observation-revision-ledger`) Actor

Track exact revisions to official U.S. BLS time-series observations and footnotes. Establish a persistent baseline, detect value and footnote changes, and emit deterministic before-and-after evidence with hashes and attribution.

- **URL**: https://apify.com/flintglade/bls-observation-revision-ledger.md
- **Developed by:** [Flintglade](https://apify.com/flintglade) (community)
- **Categories:** Automation, Other
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $3.00 / 1,000 series checkeds

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/platform/actors/running/actors-in-store#pay-per-event

## What's an Apify Actor?

Actors are a software tools running on the Apify platform, for all kinds of web data extraction and automation use cases.
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.

In JavaScript/TypeScript projects, use official [JavaScript/TypeScript client](https://docs.apify.com/api/client/js/docs.md):

```bash
npm install apify-client
```

In Python projects, use official [Python client library](https://docs.apify.com/api/client/python/docs.md):

```bash
pip install apify-client
```

In shell scripts, use [Apify CLI](https://docs.apify.com/cli/docs.md):

````bash
# MacOS / Linux
curl -fsSL https://apify.com/install-cli.sh | bash
# Windows
irm https://apify.com/install-cli.ps1 | iex
```bash

In AI frameworks, you might use the [Apify MCP server](https://docs.apify.com/integrations/mcp.md).

If your project is in a different language, use 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

## BLS Observation Revision Ledger

**BLS Observation Revision Ledger** remembers an exact official BLS Public Data API v1 query and emits one deterministic before/after report when a previously captured observation value or footnote changes.

This is not another BLS data scraper. It does not calculate growth, forecast, interpret economic conditions, or provide investment, employment, policy, or economic advice.

### Paid outcome

Each successfully retrieved series produces exactly one dataset row and one paid event:

| Outcome                                                            | PPE event                |      Price |
| ------------------------------------------------------------------ | ------------------------ | ---------: |
| First baseline or no material change                               | `series-checked`         | **$0.003** |
| One complete report containing every bounded change for the series | `series-revision-report` | **$0.020** |

Apify automatically charges the synthetic `apify-actor-start` event at **$0.00005**. This Actor never charges that event in code. The pricing definition intentionally omits `apify-default-dataset-item`, so a row is not double-charged.

A failed, malformed, partial, rejected, or spend-limit-skipped series produces no dataset row, no paid event, and no baseline change. The Actor stops before the next series when its event cannot fit within the run's maximum total charge.

### First-run and revision semantics

The first successful run for a `watchId` establishes a baseline. It returns `status: "baseline-established"`, an empty `changes` array, and makes no revision claim.

Later runs use the exact observation key `(seriesId, year, period)`:

- `added`: the key exists only in the current response;
- `removed`: the key exists only in the prior baseline;
- `value`: the exact trimmed source value changed;
- `footnote`: the canonical exact-text footnote set changed;
- `value-and-footnote`: both changed, represented by one change rather than duplicate records.

Every observation, change, series, query, and result has a deterministic SHA-256 hash. Retrieval timestamps are excluded from observation, series, query, and result hashes. Source observations and footnotes are projected onto an allowlist; values remain strings and are never coerced to floating point.

The query signature covers the normalized sorted series IDs, exact year window, and schema version. Reusing a `watchId` with a different signature fails before a BLS request unless `resetBaseline` is `true`. A reset is itself committed only after a paid result row is written successfully.

### Input

```json
{
  "seriesIds": ["CUUR0000SA0"],
  "startYear": 2024,
  "endYear": 2025,
  "watchId": "bls-cpi-baseline",
  "resetBaseline": false,
  "requestTimeoutSeconds": 20,
  "maxRetries": 3
}
````

- `seriesIds`: 1–25 unique official IDs. IDs are trimmed, sorted, and must contain only uppercase ASCII letters, digits, hyphen, or underscore, up to 64 characters.
- `startYear`, `endYear`: four-digit integers forming an inclusive window of at most ten years.
- `watchId`: 1–64 ASCII letters, digits, hyphen, or underscore. It scopes one persistent atomic baseline.
- `resetBaseline`: explicitly replace a watch, including when its exact query changes.
- `requestTimeoutSeconds`: 5–30, default 20.
- `maxRetries`: 0–4, default 3.

Unknown keys are rejected. The Actor does not accept a URL, API key, token, proxy, header, cookie, webhook, account, file, credential, or code.

### Source and bounds

The only network origin is an HTTPS POST to:

`https://api.bls.gov/publicAPI/v1/timeseries/data/`

The body contains only the documented `seriesid`, `startyear`, and `endyear` fields. The Actor makes one bounded batch request, except for retries of network failures, HTTP 408, 429, or 5xx responses. Request starts are at least one second apart; `Retry-After` is honored up to 30 seconds; each attempt has a timeout; and the response body is capped at 8 MiB.

A BLS-success status and exactly one unambiguous, complete series object are required. A missing, duplicate, empty, or malformed series is isolated as partial while complete peer series may proceed. Logs contain stable codes and bounded counts, never raw payloads.

BLS v1 supports at most 25 series and ten years and can lag a published release by one day. It returns observations and footnotes, not full series metadata.

### Output

Each paid row includes:

- schema version, watch ID, series ID, and exact year window;
- baseline, unchanged, or revised status;
- prior/current series hashes and observation count;
- the complete ordered change set for a revised series;
- the one charged event name;
- official source name and endpoint, UTC retrieval timestamp and date;
- BLS attribution, required derived-analysis disclaimer, and non-affiliation notice;
- a deterministic `recordHash` that excludes retrieval/run metadata.

No raw response, HTTP header, IP address, host/user name, environment value, local path, credential, token, or contact data is stored in a result.

### Persistent-state safety

The named Apify key-value store `flintglade-bls-revision-ledger` persists across Actor runs. One record per `watchId` holds the complete query signature and all committed series snapshots. A run stages one complete candidate watch in memory and replaces that record exactly once. The order is deliberate:

1. validate and canonicalize the bounded source batch;
2. build each complete series result against the prior committed watch;
3. verify event capacity and write each admitted row with its one PPE event;
4. stage admitted snapshots while retaining prior snapshots for partial, rejected, or spend-limit-skipped series;
5. atomically replace the watch record once after the output loop finishes cleanly.

If output or charging is interrupted, the previous complete watch remains. A clean spend stop commits only admitted series and keeps skipped series unchanged. If final persistence fails after paid rows, the previous complete watch remains; the run fails with a stable code rather than pretending the new state was committed.

Do not overlap runs that use the same `watchId`. Apify key-value records provide atomic replacement but not a cross-run compare-and-swap lock.

### Local verification

Requirements: Node.js 24+, pnpm 11.7.0, and Docker for the container gate.

```bash
pnpm install --frozen-lockfile
pnpm gate:offline
pnpm smoke:live
pnpm smoke:local
docker build --tag bls-observation-revision-ledger:rc .
pnpm smoke:docker
```

- `gate:offline` runs formatting, lint, type checking, 58+ unit/integration assertions, schema checks, compilation, the deterministic two-run fixture, and a production dependency audit.
- `smoke:live` makes two bounded requests for one known series against the fixed official endpoint and prints only hashes, counts, statuses, and event names.
- `smoke:local` runs the compiled Actor twice with a temporary local Apify store and verifies `baseline-established` then `unchanged` without retaining raw responses.
- `smoke:docker` performs the same live two-run check through the previously built image. Set `BLS_LEDGER_IMAGE` when the tag is not `bls-observation-revision-ledger:rc`.

The live commands use the public BLS service and may fail when that external service is unavailable. They never deploy or publish the Actor.

### Attribution, terms, and limitations

Source: U.S. Bureau of Labor Statistics Public Data API. Every result states its UTC retrieval date.

> BLS.gov cannot vouch for the data or analyses derived from these data after the data have been retrieved from BLS.gov.

This unofficial derived-data monitor is not affiliated with or endorsed by the U.S. Bureau of Labor Statistics. It does not use a BLS logo and must not be presented as an official BLS product. Source text is preserved; the ledger must not be used to falsely represent modified content as BLS content.

Primary policies:

- [BLS Public Data API](https://www.bls.gov/developers/)
- [BLS v1 features and limits](https://www.bls.gov/bls/api_features.htm)
- [BLS API terms of service](https://www.bls.gov/developers/termsOfService.htm)
- [Apify pay per event](https://docs.apify.com/actors/publishing/monetize/pay-per-event)

### Release status

This repository is a release candidate. Public Store publication remains blocked until the reviewed commit is reconciled byte-for-byte with the deployed build and a logged-out Store check confirms Limited permissions, hidden source files, Standby off, publisher identity, support address, event prices, platform usage included, and no default dataset-item event.

Support: `support@flintglade.com`

# Actor input Schema

## `seriesIds` (type: `array`):

One to 25 unique official BLS series IDs. IDs are trimmed but not case-normalized; use uppercase ASCII.

## `startYear` (type: `integer`):

Four-digit first year in the inclusive BLS v1 window.

## `endYear` (type: `integer`):

Four-digit final year. The inclusive window may contain at most ten years.

## `watchId` (type: `string`):

Stable identifier that scopes the persistent baseline. Reuse it only for the same exact query.

## `resetBaseline` (type: `boolean`):

Replace the watch baseline. Required when changing its series set or year window.

## `requestTimeoutSeconds` (type: `integer`):

Timeout for each request attempt to the fixed official BLS endpoint.

## `maxRetries` (type: `integer`):

Retries only network errors, HTTP 408, 429, and 5xx responses.

## Actor input object example

```json
{
  "seriesIds": [
    "CUUR0000SA0"
  ],
  "startYear": 2024,
  "endYear": 2025,
  "watchId": "bls-cpi-baseline",
  "resetBaseline": false,
  "requestTimeoutSeconds": 20,
  "maxRetries": 3
}
```

# Actor output Schema

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

Exactly one paid result for each successfully retrieved and written series.

# 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 = {};

// Run the Actor and wait for it to finish
const run = await client.actor("flintglade/bls-observation-revision-ledger").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 = {}

# Run the Actor and wait for it to finish
run = client.actor("flintglade/bls-observation-revision-ledger").call(run_input=run_input)

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

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

```

## CLI example

```bash
echo '{}' |
apify call flintglade/bls-observation-revision-ledger --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "command": "npx",
            "args": [
                "mcp-remote",
                "https://mcp.apify.com/?tools=flintglade/bls-observation-revision-ledger",
                "--header",
                "Authorization: Bearer <YOUR_API_TOKEN>"
            ]
        }
    }
}

```

## OpenAPI specification

```json
{
    "openapi": "3.0.1",
    "info": {
        "title": "BLS Observation Revision Ledger",
        "description": "Track exact revisions to official U.S. BLS time-series observations and footnotes. Establish a persistent baseline, detect value and footnote changes, and emit deterministic before-and-after evidence with hashes and attribution.",
        "version": "0.1",
        "x-build-id": "sBhkWsZfvb7oAdMuO"
    },
    "servers": [
        {
            "url": "https://api.apify.com/v2"
        }
    ],
    "paths": {
        "/acts/flintglade~bls-observation-revision-ledger/run-sync-get-dataset-items": {
            "post": {
                "operationId": "run-sync-get-dataset-items-flintglade-bls-observation-revision-ledger",
                "x-openai-isConsequential": false,
                "summary": "Executes an Actor, waits for its completion, and returns Actor's dataset items in response.",
                "tags": [
                    "Run Actor"
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "$ref": "#/components/schemas/inputSchema"
                            }
                        }
                    }
                },
                "parameters": [
                    {
                        "name": "token",
                        "in": "query",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Enter your Apify token here"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK"
                    }
                }
            }
        },
        "/acts/flintglade~bls-observation-revision-ledger/runs": {
            "post": {
                "operationId": "runs-sync-flintglade-bls-observation-revision-ledger",
                "x-openai-isConsequential": false,
                "summary": "Executes an Actor and returns information about the initiated run in response.",
                "tags": [
                    "Run Actor"
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "$ref": "#/components/schemas/inputSchema"
                            }
                        }
                    }
                },
                "parameters": [
                    {
                        "name": "token",
                        "in": "query",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Enter your Apify token here"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/runsResponseSchema"
                                }
                            }
                        }
                    }
                }
            }
        },
        "/acts/flintglade~bls-observation-revision-ledger/run-sync": {
            "post": {
                "operationId": "run-sync-flintglade-bls-observation-revision-ledger",
                "x-openai-isConsequential": false,
                "summary": "Executes an Actor, waits for completion, and returns the OUTPUT from Key-value store in response.",
                "tags": [
                    "Run Actor"
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "$ref": "#/components/schemas/inputSchema"
                            }
                        }
                    }
                },
                "parameters": [
                    {
                        "name": "token",
                        "in": "query",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Enter your Apify token here"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK"
                    }
                }
            }
        }
    },
    "components": {
        "schemas": {
            "inputSchema": {
                "type": "object",
                "required": [
                    "seriesIds",
                    "startYear",
                    "endYear",
                    "watchId"
                ],
                "properties": {
                    "seriesIds": {
                        "title": "BLS series IDs",
                        "minItems": 1,
                        "maxItems": 25,
                        "uniqueItems": true,
                        "type": "array",
                        "description": "One to 25 unique official BLS series IDs. IDs are trimmed but not case-normalized; use uppercase ASCII.",
                        "items": {
                            "type": "string",
                            "minLength": 1,
                            "maxLength": 64,
                            "pattern": "^[A-Z0-9_-]+$"
                        },
                        "default": [
                            "CUUR0000SA0"
                        ]
                    },
                    "startYear": {
                        "title": "Start year",
                        "minimum": 1000,
                        "maximum": 9999,
                        "type": "integer",
                        "description": "Four-digit first year in the inclusive BLS v1 window.",
                        "default": 2024
                    },
                    "endYear": {
                        "title": "End year",
                        "minimum": 1000,
                        "maximum": 9999,
                        "type": "integer",
                        "description": "Four-digit final year. The inclusive window may contain at most ten years.",
                        "default": 2025
                    },
                    "watchId": {
                        "title": "Watch ID",
                        "pattern": "^[A-Za-z0-9_-]+$",
                        "minLength": 1,
                        "maxLength": 64,
                        "type": "string",
                        "description": "Stable identifier that scopes the persistent baseline. Reuse it only for the same exact query.",
                        "default": "bls-cpi-baseline"
                    },
                    "resetBaseline": {
                        "title": "Reset baseline",
                        "type": "boolean",
                        "description": "Replace the watch baseline. Required when changing its series set or year window.",
                        "default": false
                    },
                    "requestTimeoutSeconds": {
                        "title": "Request timeout",
                        "minimum": 5,
                        "maximum": 30,
                        "type": "integer",
                        "description": "Timeout for each request attempt to the fixed official BLS endpoint.",
                        "default": 20
                    },
                    "maxRetries": {
                        "title": "Maximum retries",
                        "minimum": 0,
                        "maximum": 4,
                        "type": "integer",
                        "description": "Retries only network errors, HTTP 408, 429, and 5xx responses.",
                        "default": 3
                    }
                }
            },
            "runsResponseSchema": {
                "type": "object",
                "properties": {
                    "data": {
                        "type": "object",
                        "properties": {
                            "id": {
                                "type": "string"
                            },
                            "actId": {
                                "type": "string"
                            },
                            "userId": {
                                "type": "string"
                            },
                            "startedAt": {
                                "type": "string",
                                "format": "date-time",
                                "example": "2025-01-08T00:00:00.000Z"
                            },
                            "finishedAt": {
                                "type": "string",
                                "format": "date-time",
                                "example": "2025-01-08T00:00:00.000Z"
                            },
                            "status": {
                                "type": "string",
                                "example": "READY"
                            },
                            "meta": {
                                "type": "object",
                                "properties": {
                                    "origin": {
                                        "type": "string",
                                        "example": "API"
                                    },
                                    "userAgent": {
                                        "type": "string"
                                    }
                                }
                            },
                            "stats": {
                                "type": "object",
                                "properties": {
                                    "inputBodyLen": {
                                        "type": "integer",
                                        "example": 2000
                                    },
                                    "rebootCount": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "restartCount": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "resurrectCount": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "computeUnits": {
                                        "type": "integer",
                                        "example": 0
                                    }
                                }
                            },
                            "options": {
                                "type": "object",
                                "properties": {
                                    "build": {
                                        "type": "string",
                                        "example": "latest"
                                    },
                                    "timeoutSecs": {
                                        "type": "integer",
                                        "example": 300
                                    },
                                    "memoryMbytes": {
                                        "type": "integer",
                                        "example": 1024
                                    },
                                    "diskMbytes": {
                                        "type": "integer",
                                        "example": 2048
                                    }
                                }
                            },
                            "buildId": {
                                "type": "string"
                            },
                            "defaultKeyValueStoreId": {
                                "type": "string"
                            },
                            "defaultDatasetId": {
                                "type": "string"
                            },
                            "defaultRequestQueueId": {
                                "type": "string"
                            },
                            "buildNumber": {
                                "type": "string",
                                "example": "1.0.0"
                            },
                            "containerUrl": {
                                "type": "string"
                            },
                            "usage": {
                                "type": "object",
                                "properties": {
                                    "ACTOR_COMPUTE_UNITS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATASET_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATASET_WRITES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "KEY_VALUE_STORE_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "KEY_VALUE_STORE_WRITES": {
                                        "type": "integer",
                                        "example": 1
                                    },
                                    "KEY_VALUE_STORE_LISTS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "REQUEST_QUEUE_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "REQUEST_QUEUE_WRITES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATA_TRANSFER_INTERNAL_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATA_TRANSFER_EXTERNAL_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "PROXY_RESIDENTIAL_TRANSFER_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "PROXY_SERPS": {
                                        "type": "integer",
                                        "example": 0
                                    }
                                }
                            },
                            "usageTotalUsd": {
                                "type": "number",
                                "example": 0.00005
                            },
                            "usageUsd": {
                                "type": "object",
                                "properties": {
                                    "ACTOR_COMPUTE_UNITS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATASET_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATASET_WRITES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "KEY_VALUE_STORE_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "KEY_VALUE_STORE_WRITES": {
                                        "type": "number",
                                        "example": 0.00005
                                    },
                                    "KEY_VALUE_STORE_LISTS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "REQUEST_QUEUE_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "REQUEST_QUEUE_WRITES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATA_TRANSFER_INTERNAL_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATA_TRANSFER_EXTERNAL_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "PROXY_RESIDENTIAL_TRANSFER_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "PROXY_SERPS": {
                                        "type": "integer",
                                        "example": 0
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
    }
}
```
