MTG Copilot Docs
Workflows

Scheduled jobs

What runs on a timer, what each job writes, and how a job that stops running becomes visible.

Two jobs run on a timer, and one check watches them.

What is scheduled

JobScheduleWrites
MTGJSON price ingest09:00 UTC, dailyprinting_prices_current, printing_price_daily, and the card_prices headline
Freshness check10:00 UTC, dailyNothing. It reads and asserts.
Card chain11:00 UTC, dailyEverything else, when there is anything to do

MTGJSON publishes its daily price file around 08:00 UTC. The ingest runs an hour later so a late publication does not turn into a missed day.

The card chain asks before it acts

The chain imports sets, precons, printings, card text, types, embeddings and tags, in the order each depends on the one before it. That means downloading well over two gigabytes, so running it every day unconditionally would spend that most days for nothing.

It starts with one request to Scryfall's set list and compares the card total against a watermark recorded when the chain last completed. Unchanged, and it records a skip and stops. Changed, and it runs the whole sequence and moves the watermark afterwards, so a chain that dies halfway is retried the next day rather than marked done.

Daily rather than weekly, despite the name, because a set's cards appear in previews over the weeks before it releases. A weekly timer would leave them up to seven days late for no saving, since a day with nothing new costs one HTTP call.

Prices are the one thing that has to be on a timer. Every other value can be rebuilt from a vendor's current file whenever somebody runs the import, but a daily price point exists only if something captured it that day. MTGJSON's history window is about 90 days, so a day nobody captured is gone once it ages out. That is not hypothetical: 29 days went missing before anyone noticed, and recovering them was a race against that window.

Nothing asks a vendor anything at request time. Every number the app shows was written by one of these jobs beforehand.

How a stopped job becomes visible

The check at 10:00 is deliberately a separate schedule rather than a step at the end of the ingest. A check that runs at the end of a job cannot detect that job failing to start, and failing to start is the failure that actually happened here: two earlier scheduled jobs were configured, deployed, and never fired once, because the platform invoked them with a request method neither route handled. There was no failed run to look at, because there was no run.

So the check reads the data rather than the schedule. It asserts that printing_prices_current is at most two days old, that the daily series is at most two days old, that the rolled-up headline was rebuilt within two days, and a dozen other things besides. A job that stops running fails the check the next morning.

What to read for freshness

Every imported value carries when it was written, and those timestamps are the answer to "is this current", not the schedule:

ColumnAnswers
printing_prices_current.price_dateThe day that quote is from
card_prices.updated_atWhen the rolled-up cheapest rebuilt
card_popularity.updated_atWhen play ranks last moved
sealed_products.prices_updated_atThe last sealed refresh, which has no vendor. See prices

What is still manual

The chain deliberately leaves out the jobs that cost money or judgement: a full card resweep for errata, the model tagging pass, and the set guides. See imports and tagging and embeddings for what those jobs are and what each one writes.

On this page