Hex.pm Package Rankings & Download Change Monitor avatar

Hex.pm Package Rankings & Download Change Monitor

Pricing

from $0.60 / 1,000 package results

Go to Apify Store
Hex.pm Package Rankings & Download Change Monitor

Hex.pm Package Rankings & Download Change Monitor

Monitor Hex.pm package rankings. Track 90-day downloads, rank and version via the official Hex API.

Pricing

from $0.60 / 1,000 package results

Rating

0.0

(0)

Developer

Vadim Bezrukov

Vadim Bezrukov

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

1

Monthly active users

2 days ago

Last modified

Share

See which Elixir/Erlang/Gleam packages sit in the official Hex.pm top-N for 90-day downloads, whether rank, downloads or version moved since your last successful check, and whether a watched package entered or left that window. The Actor reads the published Hex HTTP API. It does not scrape HTML package pages, call /diff/, walk the whole catalog, or emit owner emails.

Use it to refresh an Elixir, Erlang or Gleam package dashboard or alert on changes in packages you follow. The default snapshot returns the current top-20 packages, one query_status and one run_summary. Export JSON or CSV, or pass the Dataset to your automation.

To start collecting history now, choose monitor + changesOnly on your first run and keep the same stateNamespace on later runs. The first monitor returns BASELINE; the second compares against that saved observation. Snapshots do not establish a monitor baseline. Hex provides no native ranking history through these endpoints, so observations begin when you start running the Actor. Default rank is 90-day recent_downloads; all-time total_downloads is a separate ranking.

By using this Actor you agree to the Hex Terms of Service. This Actor uses the published Hex HTTP API as a third-party product under those Terms and collects public package metadata only.

Choose your workflow

Start with a prepared example:

Each example opens with its saved input and a preview of the output fields.

GoalSettingsWhat you get
Export today's official top-Nsnapshot + allOne package_observation per package, all BASELINE
Re-check the same window next weekmonitor + changesOnlyNEW, UPDATED, RANK_UP, RANK_DOWN, EXITED, REAPPEARED
Watch named packageswatchlistObservations for those names; rank only if they sit in this topN window
  • Export top packages from official sort=recent_downloads order, default top-20. First run writes BASELINE rows and one query_status.
  • Monitor rank changes on the same query later. Keep stateNamespace stable. Later runs return NEW, UPDATED, RANK_UP, RANK_DOWN, EXITED or REAPPEARED.
  • Watch named packages such as jason, phoenix and telemetry. A name missing from this topN window is unranked, not NOT_FOUND, while GET /api/packages/{name} returns 200. A JSON HTTP 404 is item-level NOT_FOUND.

BASELINE identifies an initial observation. In monitor mode it is saved for the next comparison; in snapshot mode it is an export only. It does not mean the package was published today.

Quick start

{
"mode": "snapshot",
"rankingMetric": "recent_downloads",
"topN": 20
}

On the 2026-09-13 probe, rank 1 was jason and rank 2 was telemetry. Then schedule the same window:

{
"mode": "monitor",
"outputMode": "changesOnly",
"stateNamespace": "top20",
"rankingMetric": "recent_downloads",
"topN": 20
}

Keep stateNamespace stable for a given watch. Snapshot mode does not read or write history. snapshot + changesOnly is rejected before any Hex request.

A watchlist accepts phoenix or https://hex.pm/packages/phoenix. http:// URLs and other hosts are INVALID_INPUT without a fetch. Rank is the 1-based index in official list page 1, including holes: a non-object at position 2 does not make the next package rank 2. A watchlist name outside that window is unranked (rank=null). First-seen unranked names stay rank=null. EXITED is used when a prior successful observation of that id had an integer rank and then left this window for two complete successful runs. NOT_FOUND is only used when the package endpoint returns JSON HTTP 404. A complete, valid ranking plus verified package responses produces a SUCCESS query. A verified [] is NO_RESULTS on a first snapshot or first monitor, including with a watchlist; a later monitor with last-good active or pending_exits treats [] as PARTIAL / EMPTY_RANKING_LIST and does not emit EXITED.

Hex API vs this Actor API

  • Hex API: GET https://hex.pm/api/packages?sort=recent_downloads&page=1 (default) and, for watchlist ids absent from the downloaded first page, GET https://hex.pm/api/packages/{name}. Public JSON, no token. total_downloads maps to official sort=downloads.
  • Actor API: Apify input, Dataset rows (package_observation, query_status, run_summary), and optional last-good state in this Actor's key-value store.

The Actor never requests HTML /packages/{name} (Hex ToS §6 scrape ban), never requests /diff/ (robots Disallow), and never paginates page=2+ to recover a global rank. It never emits owners or email.

What changed, and what did not

Monitor diffs last successful state against current successful state.

Keep stateNamespace, rankingMetric, topN and the set of watchlist names unchanged to continue the same comparison. Changing any of these starts a separate baseline. Reordering the same watchlist does not reset history.

  • First successful monitor, including changesOnly, emits BASELINE.
  • A new id after that is NEW. An integer rank move is RANK_UP / RANK_DOWN even when 90-day or all-time downloads also moved; changed_fields still lists every fingerprint field that changed. UPDATED is used when rank did not move but downloads or latest_version did. Day and week download counters are observation fields only; they are not in the fingerprint, so day/week changes alone do not emit UPDATED. Changes to the 90-day or all-time counters do: a daily check can charge for updated packages even when every rank stays the same.
  • An id missing from two different sequential successful complete windows becomes EXITED only if a prior successful observation of that id had an integer rank. The first miss is pending_exits only. A transport error, OUTPUT_LIMIT, or mass-removal guard does not advance that counter. First-seen unranked watchlist names stay rank=null and do not EXIT. A package JSON HTTP 404 is NOT_FOUND, not EXITED.
  • EXITED means "not in this query's successful top-N / watchlist response". It does not mean the package was unpublished from Hex.
  • If a successful top-N window keeps under 50% of the previous ids at the same topN, the query is PARTIAL (mass-removal guard). Named watchlists skip that guard. Pending exits are not applied on mass-removal.
  • Broken stored state is STATE_INCOMPATIBLE for that query, not a silent new baseline. The run fails; change stateNamespace to start a new baseline.

Failure semantics

NO_RESULTS is a verified list [] with no last-good snapshot to protect. It is never a transport failure, and it is not "this watchlist id is unranked in a populated list". A later monitor that already has last-good active or pending_exits and then sees [] is PARTIAL / EMPTY_RANKING_LIST, including for named watchlists, so a blank page cannot manufacture EXITED. HTML when JSON was expected, truncated JSON, a list body over 2 MiB (SOURCE_LIMIT), HTTP 5xx, or 429 after retry are FAILED. An unparseable object inside topN is PARTIAL / INVALID_LIST and does not move state. Unparseable list slots keep distinct source_id values (hexpm:invalid-rank:{n}) and matches_query=false. A successful list GET followed by failed watchlist package GETs is PARTIAL / PARTIAL_WATCHLIST (not a run-level source failure) and does not move state. Missing packages are JSON HTTP 404; that is item-level NOT_FOUND, not an empty ranking. Invalid input writes one query_status (INVALID_INPUT) and a run_summary whose status is INVALID_INPUT (not a default snapshot), then fails the run before any Hex request. A resumed run keeps that failed outcome. If Dataset rows were already written but billing could not finish, the query is PARTIAL / SPEND_LIMIT; last-good state is still saved so the next monitor does not replay those charges.

A non-empty list shorter than topN is PARTIAL / INCOMPLETE_LIST: there is no verified complete window to compare. Repeated package identities anywhere on list page 1 are PARTIAL / DUPLICATE_PACKAGE; the Actor does not deduplicate or renumber an ambiguous ranking. Both cases emit no package rows, charge no package or window events, and keep last-good state and pending exits unchanged. A malformed package detail or a detail identifying a different package is an item-level FAILED under the requested name and makes the query PARTIAL; it cannot update state or confirm an exit.

maxPackageRows defaults to 150 so a topN of 100 plus EXITED extras can fit. It never truncates a window: if the package rows do not fit, you get PARTIAL / OUTPUT_LIMIT and the previous state is kept.

Pricing

The configured rates are listed below. The Pricing tab is authoritative.

At the default 512 MB and FREE/BRONZE event rates:

ExampleBill per run
Export top 20$0.02005
Compare 3 existing named packages$0.00305
First monitor baseline or an unchanged monitor$0.01005
Repeat monitor with 20 changed packages$0.03005
Export top 100$0.10005

A 30-run month of top-20 monitoring is $0.30150 if every run is unchanged, or $0.90150 if each run has 20 billable changes. These are calculated examples, not forecasts; confirmed exits can add change rows. The monitor formula is start + $0.01 per completed window + package rate × delivered changes. UPDATED includes changed 90-day/all-time download counts, not only rank or version changes. All first-monitor BASELINE rows are free package results in either output mode; the window fee still applies.

On private build 0.1.1 (2026-09-13, MEASURED): snapshot top-20 used one Hex list GET, ~685 KiB, no proxy, no browser, 2.2 s Actor time, platform cost about $0.00056. Buyer bill is 20 × $0.001 + start $0.00005 = $0.02005. A quiet second monitor of the same window billed start + window-check = $0.01005 and charged no package-result. These are historical private validation measurements, not a runtime guarantee.

EventWhen it is chargedFREE / BRONZE
apify-actor-startPlatform start; one event at the default 512 MB. The Actor never emits it.$0.00005
package-resultEach successful package row in a complete snapshot. In monitor, only NEW / UPDATED / RANK_* / EXITED / REAPPEARED from a complete check.$0.001
window-checkMonitor mode, fully delivered window, including NO_RESULTS and zero-change. Not charged for PARTIAL or FAILED.$0.01

Verified missing packages (NOT_FOUND) and diagnostic rows never incur a package-result fee. Invalid input and source failures incur no custom events; the platform start fee still applies once a run starts. If all data was already delivered and the SDK accepts only part of a charge, the final query becomes PARTIAL / SPEND_LIMIT; accepted charges remain visible in RUN_SUMMARY.

package-result is $0.0008 on SILVER and $0.0006 on GOLD+. Platform usage is included, not passed through. apify-default-dataset-item is off: enabling it would double-bill every Dataset row.

A run cap must cover start plus the events: $0.02 is not enough for the sample top-20, and $0.01 is not enough for a quiet monitor. The configured minimum cap is $0.021 so the Store prefill can finish. This is a minimum budget, not a minimum bill: a three-package snapshot still costs $0.00305. The Actor default budget is $0.50. Snapshot of 20 packages → 1 × $0.00005 start + 20 × $0.001 = $0.02005. Quiet or first monitor of the same top-20 → start + $0.01 window-check = $0.01005 and $0 change rows. Retries, FAILED and PARTIAL are not charged as package-result or window-check (the platform still charges start). If Dataset rows were already written and billing then hits the cap, window-check may already have been charged; that query is PARTIAL / SPEND_LIMIT and last-good state is saved.

Limitations

  • There is no native Hex history comparable to storing yesterday's JSON yourself. curl of the same public endpoints remains free. Per-version daily download series are outside this Actor's scope.
  • The Actor does not cover the whole Hex catalog. It never paginates the catalog.
  • EXITED tombstones are kept 180 days, at most 20 000 per query. After prune, a very old return may classify as NEW.
  • MVP assumes one writer per stateNamespace and sequential runs. There is no distributed lock.
  • Watchlist package GETs use concurrency 2 and are not exposed in Input.

Legal review on 2026-09-13: https://hex.pm/robots.txt disallows only /diff/. /api/ is allowed. Hex ToS §4 permits API use through a third-party product; §6 prohibits scraping outside published interfaces. Re-read robots and the Hex Terms of Service before Store publication; a later Disallow: /api/ is a stop, not something to bypass.

Use from AI agents with MCP

After Store publication, connect using your own Apify account through https://mcp.apify.com?tools=automa-flow/hex-pm-package-rankings-monitor. Ask the agent to keep mode: monitor and outputMode: changesOnly on repeat runs, and to treat NO_RESULTS as a verified empty window, not a source failure. Execution requires your own authentication and run budget. Public search discovery becomes verifiable after publication; a private Actor is not expected in anonymous search. Autonomous agentic payments are not a verified channel for this Actor.

Automation example (n8n / Make)

This is a copy-paste illustration, not a hosted integration promise.

  1. Save the monitor JSON above as a Task. In Apify Schedules, use 0 8 * * * in UTC for a daily 08:00 check and attach that Task.
  2. Add an ACTOR.RUN.SUCCEEDED webhook pointing to your n8n/Make webhook URL. Read resource.defaultDatasetId from the notification and fetch its items with your own Apify connection. Inspect query_status first: a run can finish with a PARTIAL business outcome.
  3. Keep rows where record_type=package_observation and change_type is one of NEW, UPDATED, RANK_UP, RANK_DOWN, EXITED, REAPPEARED.
  4. Open source_url for the human check. Ignore EXITED as "unpublished from Hex".

Protocol notes

User-Agent: automa-flow-hex-pm-package-rankings-monitor/0.1. Origin is fixed to https://hex.pm. Redirects off documented /api/ paths fail the window. Timeout 20 s. A list body over 2 MiB is FAILED / SOURCE_LIMIT. List GET concurrency is 1. Watchlist GETs use concurrency 2 and are not exposed in Input.