A scan you asked for is a snapshot. The value of a vulnerability platform is mostly in the run nobody asked for — the one that notices a critical published against an image that has not changed since it was deployed. By the end of this tutorial one of your assets will be rescanned on a recurring schedule, and you will be able to tell whether that schedule is actually working. Allow about fifteen minutes, most of it waiting for the first run.

Before you start

You need a completed scan of a tagged image — alpine:3.19, not alpine@sha256:…. Why that matters is step 2. The Quickstart produces exactly the right thing.

1. Find the asset

A schedule belongs to an asset, not to a scan.
The key you use here needs schedules:write. Reading a schedule needs schedules:read.

2. Set the schedule

PUT, not POST, because a schedule is a singleton per asset: sending the same request twice must leave one schedule, and a partial unique index enforces that even under a race. Three things about this request are worth understanding rather than copying. The frequency is a preset. hourly, daily or weekly, and nothing else — there are no cron expressions. A cron expression brings timezone semantics, daylight saving and “the 31st of February”, an entire class of bug, in exchange for letting you pick 03:00. Nobody needs a container rescan at a specific minute; they need it often enough to notice a newly published critical within a working day. Intervals are stored in seconds and everything is UTC, which is precisely what “every 24 hours” needs so that it does not become “every 23 or 25 hours” twice a year. Container images only, and only tagged ones. An asset first seen through a digest-pinned reference has no tag to re-resolve. A “rescan” of immutable content would either re-read identical bytes forever or, if the missing tag were defaulted, silently scan :latest — a different image from the one you believe you are watching. The request is refused at creation with a message saying so, and refused again at run time as defence in depth. The first run is immediate. It is not one interval out. A user who turns on daily scanning wants to know the state of the asset today; waiting 24 hours for the feature to do anything reads as it being broken. The occurrence phase is then set by that first run, so a schedule enabled at 09:00 runs at 09:00 thereafter.

3. Watch the first run happen

Within a minute or so you should see last_run_at set and next_run_at about a day ahead. A new scan will have appeared on the asset:
A scheduled scan and one you clicked are the same job from the moment they exist: same queue, same worker, same retry, same reconciliation against the existing findings. There is no separate execution path, because a second path would acquire a second set of bugs. That reconciliation is what makes a schedule worth having. Each run keeps every decision you recorded — the triage, the suppression with its reason — and moves only what actually changed: a finding the run no longer sees is resolved, one that was resolved and is seen again is reopened.

4. Read what the schedule says about itself

Three fields on the schedule are health, not configuration, and they are the reason to look at this endpoint again in a week.
missed_runs counts occurrences that came due while TRUSTIVAN was not running. If the platform is down for thirty days, thirty daily runs come due at once — and TRUSTIVAN runs one, advances in whole intervals past now, and counts the rest. Replaying all thirty would be thirty identical scans of one image telling you nothing the last one alone would not have. Silently setting the next run to “now plus a day” would make a week of downtime indistinguishable from a week of successful runs. So the gap is collapsed and then reported, because a gap in coverage nobody is told about is indistinguishable from continuous coverage. Advancing from the planned time rather than from now preserves the phase: a schedule that ran at 02:00 keeps running at 02:00. consecutive_failures and last_error are what a failing schedule looks like. On failure, last_run_at is deliberately not advanced — a run that failed did not happen, and advancing it would let a schedule whose registry became unreachable report “last run: this morning” forever while scanning nothing. The next run moves to a backoff rather than a full interval, so a transient registry outage costs minutes; the backoff never exceeds the schedule’s own next occurrence, so it cannot quietly turn a daily schedule into a weekly one. A schedule never gives up: the asset is still yours, and a security product deciding on its own to stop looking is the wrong failure mode. Error messages here are sanitised before they are stored, because registry errors embed URLs and a URL can carry a token.

5. Pause or remove it

Disabling keeps the schedule and its history:
Removing it entirely:
204 on success. An asset with no schedule answers 404 on the read — not 200 with a null — because an asset with no schedule and an asset that is not yours must look identical.

One thing that happens without you

Each run rebuilds the image reference from the asset’s stored coordinates and pushes it back through validation against the current configuration, rather than reusing the string from the last scan. A schedule created while private registries were permitted outlives that setting; if an operator turns private registries off, every stored reference pointing at an internal address must stop being fetched, including the ones already scheduled. Trusting the stored string would leave a permanent, self-renewing exception to a security setting, created before the setting changed and invisible afterwards. The practical consequence: a schedule can start failing because a policy was tightened, not because anything about your image changed. last_error will say so.

What you have now

An image that is checked every day without anyone asking, whose findings keep their identity and their decisions across every run, and whose schedule tells you when it has been failing or when coverage has a gap. From here: