# USAspending Contract Modification Transaction Ledger (`thatmike1/usaspending-modifications`) Actor

Export all published transactions for specified federal contract awards, up to 5,000 per award. Keep zero-dollar actions, deobligations and observed corrections, with optional daily change tracking.

- **URL**: https://apify.com/thatmike1/usaspending-modifications.md
- **Developed by:** [Michal Pšenčík](https://apify.com/thatmike1) (community)
- **Categories:** Business, Automation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.00005 / actor start

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?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
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.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## 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.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## USAspending Contract Modification Transaction Ledger

Export every currently published transaction for the federal contract awards you name, straight from the official
[USAspending API](https://api.usaspending.gov), **up to 5,000 transactions per award**. Zero-dollar modifications,
deobligations (negative amounts) and the base award action are all kept, one row per transaction, with signed amounts
as exact two-decimal strings. Optionally keep a baseline in your own account and, on later runs, receive only
first-seen transactions and corrected versions of earlier ones, with the previous values attached.

### What it does

For each award ID (up to 20 per run) the actor makes four requests to `api.usaspending.gov`: the award detail (to
prove the award exists and is a contract of type A, B, C or D), the transaction count, one transaction list of up to
5,000 rows, and the count again. If the two counts and the returned rows agree, every transaction is delivered,
sorted by action date and then transaction ID. Anything that does not agree is reported in the free SUMMARY record
instead of being delivered as if it were complete.

- **Not unlimited history.** Awards with more than 5,000 current transactions are reported as
  `history_limit_exceeded` and get no rows. Later pages are never fetched, because USAspending's paging is not a
  stable snapshot.
- **Current published data only.** USAspending publishes with a delay that varies; there is no freshness guarantee,
  and the source keeps no history of earlier versions. Corrections are found by comparing full rescans with your own
  saved baseline, from the first run of a watch onward.
- A 404 for an award is `award_not_found`, never an empty ledger. A missing transaction is never reported as deleted.

### Input

| Field               | Meaning                                                                                                                                                                                                                                         |
| ------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `awardIds`          | 1-20 generated contract award IDs starting with `CONT_AWD_`, as in the award page URL, e.g. `CONT_AWD_DENA0003525_8900_-NONE-_-NONE-`. Numeric IDs, bare PIIDs, URLs, IDVs, grants and loans are rejected.                                      |
| `watchName`         | Empty for a one-off export (every run delivers and bills every transaction again). A name keeps a baseline in a named key-value store `usaspending-ledger-v1-…` in your account; later runs deliver only first-seen and corrected transactions. |
| `maxRows`           | Most rows stored per run, 1-5,000 (default 1,000). With a named watch, continue rows past it using SUMMARY.resumeInput. For stateless exports set it large enough for the whole result; capped reruns re-deliver and re-bill the first rows.    |
| `requestIntervalMs` | Idle time after each request, 1,200-10,000 ms (default 1,200).                                                                                                                                                                                  |

Run one watch at a time: a second run of the same watch while one is active stops with `watch_overlap`. A watch holds
at most 20,000 transactions, 100 awards and 16 MiB of saved state, one snapshot of at most 8 MiB per award, and it
keeps room to rewrite its largest award snapshot, so the usable space is 16 MiB minus that snapshot (and a few hundred bytes for its bookkeeping). It never drops
old transactions to make room. A full watch still delivers corrections that fit and reports the changes that do not
as `state_limit` ("The watch is full"). When later receipt writes might have freed capacity, the refused award stays
pending for recheck on the next named watch run. A refusal with no later receipt writes is excluded from pending;
use a new watch name if it stays full.

### Output

One dataset row per delivered transaction version: `award_id`, `transaction_id`, `action_date` (date only),
`action_type` and description, `modification_number` (text, leading zeros kept), `description`,
`federal_action_obligation` (signed USD of this action, not the award total), the two other source money fields,
`event_kind` (`first_seen_transaction` or `corrected_transaction`), `content_hash`, `previous_content_hash` and
`previous_values` for corrections, `observed_at` (when this actor read it, not when it was published), a `ledger_key`
to upsert on, and `source` with the exact request and the raw response's SHA-256. The base action has null action
type fields, as published.

The free `SUMMARY` record in the default key-value store reports per award: status, both counts, coverage, rows
stored, unchanged count, warnings and any error (with HTTP status and Retry-After). `pendingAwardIds` and
`resumeInput` describe unfinished work after a cap. **Continuation requires a named watch.** Without one,
`pendingAwardIds` is diagnostic and `resumeInput` is null: every new run starts without a baseline and re-delivers
and re-bills the first rows. A stateless export needs `maxRows` large enough for the whole result across all input
awards. If the 5,000-row run maximum cannot cover them, split the award list into smaller runs. Other time, request,
response and spending limits still apply.

### Pricing

Pay per event: **$0.003 per stored transaction version** (first-seen or corrected) plus **$0.00005 per run start**.
Errors, empty checks, unchanged scans and SUMMARY are free; a quiet watch run costs only the start event. The example
award's 314-transaction backfill is $0.94205. A stateless run bills every row again each time.

### Limits

20 awards, 80 source requests and about 180 seconds of work per run; 20 seconds per request; 4 MiB per response and
32 MiB per run. A 403, 429, challenge page or redirect stops all requests for the rest of the run, and a 429 is not
retried; the SUMMARY says when to try again. No proxy, browser, cookies or login is ever used.

### Source and data

Data comes from the public USAspending API run by the U.S. Treasury; no key or account is needed. Rows contain
published contract action data only; descriptions may contain names as the government published them. You are the
controller of the data you export. An award's presence in USAspending does not mean it is active or a sales lead.

# Actor input Schema

## `awardIds` (type: `array`):

1 to 20 generated USAspending contract award IDs, each starting with CONT_AWD\_ (for example CONT_AWD_DENA0003525_8900\_-NONE-\_-NONE-, the ID in the award page URL). Numeric award IDs, bare PIIDs, URLs, IDVs, grants and loans are rejected. Duplicates are skipped and reported in SUMMARY.

## `watchName` (type: `string`):

Leave empty for a stateless export; each run re-delivers and re-bills the first rows, so maxRows must cover the whole result. Continuation requires a named watch. Set a name (1-64 letters, digits, underscores or hyphens) to keep a baseline in a named key-value store in your account: later runs with the same name deliver only first-seen transactions and corrected versions, and an unchanged scan stores no rows. Run one watch at a time. A watch holds at most 20,000 transactions, 100 awards and 16 MiB of saved state (at most 8 MiB per award).

## `maxRows` (type: `integer`):

Most transaction rows this run stores in the dataset (1-5,000), across all awards. With a named watch, rows beyond it stay pending; continue using SUMMARY.resumeInput. Stateless runs have no continuation: set this cap large enough for the whole result or split awardIds into smaller runs; capped reruns re-deliver and re-bill their first rows.

## `requestIntervalMs` (type: `integer`):

Idle time after each USAspending request completes, including retries (1,200-10,000 ms).

## Actor input object example

```json
{
  "awardIds": [
    "CONT_AWD_DENA0003525_8900_-NONE-_-NONE-"
  ],
  "maxRows": 1000,
  "requestIntervalMs": 1200
}
```

# Actor output Schema

## `transactions` (type: `string`):

No description

## `summary` (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 = {
    "awardIds": [
        "CONT_AWD_DENA0003525_8900_-NONE-_-NONE-"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("thatmike1/usaspending-modifications").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 = { "awardIds": ["CONT_AWD_DENA0003525_8900_-NONE-_-NONE-"] }

# Run the Actor and wait for it to finish
run = client.actor("thatmike1/usaspending-modifications").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 '{
  "awardIds": [
    "CONT_AWD_DENA0003525_8900_-NONE-_-NONE-"
  ]
}' |
apify call thatmike1/usaspending-modifications --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,thatmike1/usaspending-modifications"
        }
    }
}
```

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/Eu6V60FdRyBOKPMY2/builds/CzsziO8LXvJcpscgm/openapi.json
