Federal contracts ending in 3-18 months (USAspending.gov) with incumbent and SAM.gov check. Soonest 25 per run, or only new/changed with a saved monitor name.
No duplicate row after an interrupted run. If a run was interrupted while saving a batch that contained a changed contract
(or a new SAM.gov notice alert), the next run sent that row twice: once free of charge as a re-send, then again as "changed", which
was charged. The re-send now also records the row as delivered, so it appears once and is not charged again.
No old rows re-sent after switching versions or resetting. A batch left over from an interrupted run is now checked against
what has been saved since. If a later run (for example an older version after a rollback) has already delivered a newer version of
a contract, the old copy is no longer re-sent. A run with "Reset saved state" now also clears such a leftover batch, even when it
returns no rows. Rows are never lost: a leftover row that nobody has delivered since is still re-sent free of charge.
README: clearer note on how to continue saved state from versions up to 0.2.2 (monitor name 813100bcbbf9).
No changes to pricing, inputs, output fields or the rows of normal runs.
0.2.10 (live since Oct 16, 2026)
No rows skipped when storage stays down mid-save. Before each batch of up to 10 rows is saved as delivered, the batch is now
first written to a separate "pending" record. If Apify's storage then stays down partway through the save, the run stops and the
next run on the same monitor re-sends that batch free of charge, and counts it as delivered. Before, up to 10 such rows could be
skipped (never delivered, never charged). You are never charged twice for a row.
The default-filter run no longer touches your saved data. A run without a monitor name that uses the Store's default filters
(an empty input {}, the prefilled example, or an empty stateKey/monitorName, as in Apify's daily test) no longer opens, reads
or creates the Actor's saved-state storage. It still returns the 25 soonest-ending contracts. The only thing it no longer does is
look for very old saved state from before monitor names and print the "set monitorName … to get only new results" hint. Parent
companies on such a run are looked up fresh instead of from the shared cache.
Fewer storage writes. The shared parent-company cache is only saved when a run actually looked up parent companies.
Runs with your own filters but no monitor name, and all runs with a monitor name, return the same rows and charges as 0.2.9.
No changes to pricing, inputs or output fields.
0.2.9 (live since Oct 16, 2026)
A brief storage hiccup no longer fails a good run. When saving what a monitor has already returned hits a short error in Apify's
storage, the save is tried again up to twice (after a quarter second, then under a second). Before, one such error could fail an
otherwise successful run or, in a rare case, skip up to 10 rows. Retrying never charges you twice: the save always happens before
the rows are charged, and a retry just saves the same thing again. If storage stays down, the run stops. Only a batch caught in
the final save of a run is then re-sent free by the next run; a batch whose save failed at its first step is simply delivered (and
charged once) by the next run, and in 0.2.9 a batch caught partway through its save could still be skipped (fixed in 0.2.10).
Store name. The README and listing files now use the Actor's current Store title, "Expiring Federal Contracts – Recompete Radar".
GovCon suite links use the current names of the four Actors and now include Federal Contract Awards – New Winners Feed (who just
won contracts in your NAICS).
Accurate lookup counts when a time limit is hit. When parent-company or potential-end-date lookups run out of their time
budget, the run stats now always count the unfinished lookup as skipped. Before, on a busy machine it could be counted as a failure,
and repeated miscounts could make the log say the service was failing. Only the run stats, the log and the status note change
(the "lookups were slow or failing" note now also appears when the time limit hits on the last lookup); the rows you get, the
parent-company cache and what you pay are unchanged.
No changes to pricing, inputs or output fields. An empty stateKey still works the same as leaving it out (now covered by a test).
0.2.3 (beta, not yet promoted)
Monitor name / stateless default. New monitorName input (stateKey still works). With a monitor name each run returns only new or
changed contracts (100 rows by default). Without one, a run is stateless: the 25 soonest-ending contracts, nothing saved.
Existing users are grandfathered. A run without a monitor name whose store already has the saved state that 0.2.2 / 0.1.x kept for
runs without stateKey keeps its "new or changed only" tracking on that state, exactly as before. The state is keyed by a hash of the
filters, in either format: FP2-STATE-<hash> or the older STATE-<hash>. Only new or changed rows are charged. The run logs: "Stateless
(25 soonest-ending rows) is now the default for runs without a monitor name; your existing saved state is kept, … Set monitorName (or
stateKey) to "" to keep this behaviour explicitly." Users with no such state get the stateless default.
The status message shows the short form: 'Legacy no-name state kept; set monitorName "" to keep it.'
Except the Store's default filters (review B1). A run with the Store prefill filters, or with {} (which gets the same Store
defaults and the same filter hash, STATE-813100bcbbf9 on the platform), is never grandfathered: it is always stateless and returns
the soonest-ending rows (25 by default), so Apify's daily test can't come back empty on an account with old saved state. If saved
state exists for that filter set it is only read, never written, and the run logs the monitor name that keeps it ("set monitorName
(or stateKey) to "813100bcbbf9""); the status message says 'Saved state from an earlier version found; set monitorName "" to
get only new results from it.' Every other filter set is grandfathered as before.
What this costs (C3). A run with the Store's default filters (prefill or {}) and no monitor name is now charged for up to 25
rows every run, even when nothing changed (on 0.2.2 such a run only returned and charged new or changed rows). To keep paying only for
new or changed contracts, set the monitor name shown in the run's notice (monitorName, e.g. "813100bcbbf9").
Clearer default-filter message. "No filter given" now reads "No NAICS, PSC, agency or incumbent filter was given, so the Store
defaults were used: … minimum award $X (your value / Store default)", because a run with only e.g. minAwardAmount did give a filter.
Status message fits the platform's 500-character limit. Lower-priority notes are dropped whole instead of being cut off.
New SAM.gov notice alert. A contract comes back as changed with changeReason: samNotice when a new high- or medium-confidence
SAM.gov notice matches it. This is only checked on runs where the SAM.gov join worked. changeReason is award for end date, value or incumbent changes.
Run lock. A dead run's lock is taken over: at once if its status shows it is no longer running, otherwise after 10 min without
a heartbeat (Actors run with limited permissions and usually can't read other runs). The lock is refreshed every minute and released
on success, on errors and on abort: the platform's graceful abort ('aborting' event) waits for the release, and so does a SIGTERM that
follows it (review B2), including an abort that lands while the lock is still being acquired.
Lock re-checked before every batch (review S1), lost-lock flag before every row (C1). A run that loses its lock to another run
while pushing stops at once: no more rows, charges or state writes; the rest of the interrupted batch is re-sent free by a later run.
Lock release on SIGTERM / SIGINT and before the run timeout (C2). SIGTERM and SIGINT release the lock (awaited) before exiting. On
the platform a forced abort and a run timeout kill the run at once (measured: ~0.1 s, no time for any handler), so a monitor run now
stops itself about 15 s before its timeout (status FAILED with an explanation), keeping what it returned and releasing the lock. A
forced abort or an out-of-memory crash still leaves the lock for up to 10 minutes.
No rows lost when a run stops during a state save (C4, beta-023k). A stop (platform abort, SIGTERM/SIGINT, the deadline guard, a
lost lock) that landed between the per-batch FP2 and META2 writes could mark up to 10 rows as delivered without pushing them (lost,
never charged). Lock release now waits for a state write in flight, so the batch is always saved as pending and re-sent free by the
next run; once the lock is lost or released no state write starts (per-batch, final and crash-recovery saves), and the heartbeat stops
after a takeover. Reviewer tests R13 (deadline guard) and R15 (abort) plus a SIGTERM variant.
Docs / Store text. README "Only new or changed" now says a new high- or medium-confidence SAM.gov notice also returns a contract as
changed (samNotice), as the code does. .actor/actor.json description / seoDescription and STORE_LISTING.md carry the live Store
text ("Soonest 25 per run, or only new/changed with a saved monitor name"), so a later push can't bring back "New only".
Withheld notice alerts are kept (review S2). A samNotice alert cut by maxItems or the spending limit is delivered by a later run
(before, its notice was saved as seen and the alert was lost).
Bounded slow sources. The SAM.gov extract is dropped after 60 s without data or 6 min in total. Potential-end-date lookups get a
3-failure circuit breaker and a 90 s deadline, like parent lookups.
Crash safety. State is saved per batch of 10 before charging. After a crash, the interrupted batch is re-sent free.
Invalid SAM.gov API key. Reported plainly ("SAM.gov rejected the API key"); the join is skipped and nothing is re-sent or charged
because of it.
Roll-forward. A row that an older version delivered as changed during a rollback is not charged again.
0.2.2
Delta fingerprint v2 (award fields only), roll-forward import of rows delivered by 0.1.x, README "changed" definition, build tag beta.