EN

Theme

Accent colour

Your theme and colour carry over to satelit.md and the app.

Dashboard Choose a plan

Legislative digest

Every morning, what was published in Monitorul Oficial, on the topics and issuers you care about. An HTML file ready to send by email.

How it works

  1. Find which days have a digest, from /v1/law/digests.
  2. Fetch the digest of the newest day.
  3. Filter the acts by your topics and issuers. Important acts go first.
  4. Write digest-YYYY-MM-DD.html, with links to each act's official page.

What it costs: 2 credits for the list, 2 for the digest: 4 credits a day, 120 a month. It fits in the Start plan. From the Creator plan you can receive the law.digest event instead of polling.

How to run it

bash
export SATELIT_KEY=sat_live_...
TOPICS=business,justice ISSUERS=guvernul node law_digest.mjs

What it outputs

stdout
digest-2026-10-31.html: 6 of 20 acts, 4 credits

The full script

law_digest.mjs
#!/usr/bin/env node
// Law digest: what Monitorul Oficial published, on your topics and issuers, as an HTML file ready to e-mail.
//   SATELIT_KEY=sat_live_... TOPICS=taxes,business ISSUERS=guvernul node law_digest.mjs
// No dependencies, Node 18 or newer. Put it in cron: 0 8 * * 1-6 node /path/law_digest.mjs
import { writeFileSync } from "node:fs";

const BASE = process.env.SATELIT_BASE ?? "https://api.satelit.md/v1";
const KEY = process.env.SATELIT_KEY;
const LANG = process.env.DIGEST_LANG ?? "ro";
const TOPICS = (process.env.TOPICS ?? "").split(",").filter(Boolean);
const ISSUERS = (process.env.ISSUERS ?? "").split(",").filter(Boolean);
if (!KEY) throw new Error("Set SATELIT_KEY");

async function api(path, params = {}) {
  const url = new URL(BASE + path);
  for (const [k, v] of Object.entries({ lang: LANG, ...params })) url.searchParams.set(k, v);
  const res = await fetch(url, { headers: { Authorization: `Bearer ${KEY}` } });
  const body = await res.json();
  if (!res.ok) throw new Error(`${path}: ${body.error.code}: ${body.error.message}`);
  return body;
}

const esc = (s = "") => String(s).replace(/[&<>"]/g, (c) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;" })[c]);

const days = await api("/law/digests", { days: 7 });
if (!days.data.length) {
  console.log("No digest in the last 7 days");
  process.exit(0);
}
const { date } = days.data[0];
const digest = await api(`/law/digests/${date}`);

const wanted = (act) =>
  (!TOPICS.length || TOPICS.includes(act.topic)) && (!ISSUERS.length || ISSUERS.includes(act.issuer_key));
const acts = digest.data.items.filter(wanted).sort((a, b) => Number(b.important) - Number(a.important));

const rows = acts.map((a) =>
  `<li${a.important ? ' style="font-weight:600"' : ""}><a href="${esc(a.url)}">${esc(a.heading)}</a><br><small>${esc(a.issuer_name)}, ${esc(a.mo_ref)}</small></li>`,
);
const html = `<!doctype html><html lang="${LANG}"><meta charset="utf-8"><title>${esc(digest.data.title)}</title>
<h1>${esc(digest.data.title)}</h1><p>${esc(digest.data.intro ?? "")}</p>
<ul>${rows.join("\n") || "<li>0</li>"}</ul>
<p><small>${esc(digest.meta.attribution ?? "")}</small></p></html>\n`;

const file = `digest-${date}.html`;
writeFileSync(file, html);
console.log(`${file}: ${acts.length} of ${digest.data.items.length} acts, ${days.meta.credits + digest.meta.credits} credits`);