New and open U.S. federal grant opportunities from the official Grants.gov API: title, agency, deadline, award ceiling and floor, eligibility, Assistance Listings, full description. Daily feed with dedupe and filters. Not endorsed or certified by HHS.
Unreleased: invalid input ends SUCCEEDED with a free status row; 256 MB default memory - 2026-11-04 (local, not built or pushed; actor.json version unchanged: 0.1)
Invalid input no longer FAILS the run. Input that parse_input rejects (e.g. statuses: ["open"], agencies: ["HHS!"], aln: "93866", onlyNewSinceLastRun: "maybe", a funding filter with includeDetails: false, maxItems: "abc") used to call Actor.fail (run FAILED, exit 1, status message = the reason). The run now ends SUCCEEDED (exit 0) with one free status row {"recordType": "status", "type": "status", "status": "error", "statusRow": true, "message": "Invalid input, nothing was fetched or charged: <reason>", "error": "<reason>", "charged": false, "scrapedAt": ...} (pushed with no charged event) and the same message as the terminal status message. No request is made, no opportunity is charged and no RUN_SUMMARY is saved. The prefix is "nothing was fetched or charged" (not "...and no results were charged") because this Actor's pricing has no start event; the reason texts are unchanged.
Wrong-type crashes (uncaught OverflowError/AttributeError, exit 91, e.g. a number = Infinity or a non-string onlyNewSinceLastRun) now give the same row with the reason a field has the wrong type (<python error>).
A whole input that is not a JSON object (a string, a number, a list of objects) used to crash with AttributeError (exit 91); it now gives the same row with the reason the input must be a JSON object, got <type>. An empty list or null input still means {}.
Real upstream and runtime failures are unchanged: the existing "Not charged" source-down row and the "no new items" warning row stay as they were.
Memory:.actor/actor.jsondefaultMemoryMbytes 1024 -> 256, defaultRunOptions.memoryMbytes 1024 -> 256, minMemoryMbytes 256 -> 128 (max stays 2048). Local peak RSS is about 70 MB. The README had no memory or start-event text to change.
Pricing, default input, prefill and exampleRunInput unchanged.
Files: my_actor/main.py (new end_invalid_input, wrong-type wrap around parse_input, non-object check), .actor/actor.json, README.md ("Invalid input" FAQ line), requirements.txt, CHANGELOG.md, tests/test_invalid_input.py (new).
Unreleased: free status row when nothing is new - 2026-10-31 (actor.json version unchanged: 0.1)
When only-new is on (onlyNewSinceLastRun 'yes', or 'auto' in the default daily mode), Grants.gov answered, at least one opportunity was skipped as already delivered (same stateKey) and 0 data rows were saved, the run now pushes one row {recordType: "status", status: "warning", message: "Not charged: no new items since last run. ...", source, host, skippedSeen, stateKey, checkedAt} with no charged event (only the free default dataset-item event), logs a warning, mentions it in the status message and sets statusRow: true in RUN_SUMMARY. Same shape as the source-down row (status_row()), with status "warning" instead of "error". Not pushed when data was saved, when the source failed (the source-down "error" row is pushed instead, unchanged), when the cost limit stopped the run, when only-new is off, or when nothing was skipped as seen. This replaces the note in the entry below that runs finding nothing new are unchanged.
Files: my_actor/main.py (new push_no_new_status(), called after push_status_if_empty() at the end of run()), README.md ("Nothing new" FAQ line), tests/test_no_new_status.py (new, 5 tests). Tests: 53 (was 48) passed. Pricing, defaults and data rows unchanged.
Unreleased: cost wording - 2026-10-31 (actor.json version unchanged: 0.1; no code or pricing change)
Comments and docs no longer say a $0.00005 platform start event is charged: pricing has only the per-item event
(grant-opportunity $0.0015) and no start event. {} cost math is per-item only: 6 x $0.0015 = $0.009; a start
event would apply only if one were configured. Files: my_actor/main.py (comment above DEFAULT_MAX_ITEMS),
pricing.json (maxItemsDefaultCap note), tests/test_default_cost.py (docstring; no start event added, limit $0.010),
and the wording of the older 2026-10-31 entry below. Pricing logic, defaults and charging are unchanged.
Unreleased: free status row when the source is down - 2026-10-31 (actor.json version unchanged: 0.1)
Added: one free recordType: "status" row when nothing could be read
A local run with the network down used to finish SUCCEEDED with 0 items and nothing in the dataset, so the user saw an
empty result with no explanation. Now, when Grants.gov cannot be reached or keeps answering with errors (robots.txt
unreadable, DNS/connection failure, repeated 5xx, 403, a non-JSON answer) and 0 data rows were saved, the run pushes one
row {recordType: "status", status: "error", message: "Not charged: ..."} with no charged event (only the
free default dataset-item event), logs a warning, mentions it in the status message and sets statusRow: true in
RUN_SUMMARY. The run still exits 0 / SUCCEEDED. Runs that saved data, and runs that simply found nothing new, are unchanged.
Files changed:
my_actor/main.py: new status_row() and push_status_if_empty(), called at the end of run(); the early-stop
handler also catches httpx.HTTPError and ValueError (non-JSON answer) so they stop cleanly instead of crashing.
README.md: new "Source down" FAQ line.
tests/test_netdown_status.py: new (DNS down, HTTP 500, non-JSON; helper is quiet when data was saved or nothing failed).
Ranger's cloud {} run of build 0.1.2 stopped at the $0.02 max-charge cap (13 opportunities x $0.0015). {}, a
blank maxItems and the Console prefill now stop at 6 opportunities x $0.0015 = $0.009 (per-item events only; a start
event would apply only if one is configured), so the Store's empty-input test stays at about $0.01. Raise maxItems for a full daily feed.
Files changed:
my_actor/main.py: new DEFAULT_MAX_ITEMS = 6, used by parse_input (was a literal 500).
.actor/input_schema.json: maxItems default 500 -> 6, prefill 100 -> 6, description states the cost.
.actor/actor.json: new exampleRunInput (the prefill: posted + forecasted, last 7 days, open only, details on,
maxItems 6). The version is still 0.1.
pricing.json: estimatedCostExamples.maxItemsDefaultCap now describes the 6-item default.
README.md: Price section says {} and the prefill stop at 6 opportunities, at most $0.009.
tests/test_input.py: {} default is 6. New tests/test_default_cost.py: code default == schema default ==
prefill == exampleRunInput.maxItems == 6, cost <= $0.010 (per-item only), README states it.