Documentation / Watches
Watches
A watch re-runs a check on one VAT number on its own — monthly, weekly or daily — and records what changed since the previous run. Each run is a normal check: it consumes quota, is deduplicated over 24 h and leaves its own proof.
How many numbers an account may watch is set by its plan (free 0, starter 200, business 2 000, pro 10 000). Weekly monitoring is sold from business up, daily monitoring with pro only; starter watches monthly. A daily watch always asks the authority again: its next run is scheduled a full day after the previous one actually ran, past the 24 h VIES cache and the 24 h deduplication window, and each run costs one check of the quota. Watches beyond the plan's limit — after a downgrade, say — are paused, never deleted: they stop running and stop consuming quota, and they resume as they were once the plan allows them again.
The watch object #
{
"watch_id": "wat_01J9X0000000000000000000",
"vat_number": "FR40303265045",
"country": "FR",
"label": "Chanel",
"frequency": "monthly",
"candidate": { "name": "CHANEL", "street": "135 AV CHARLES DE GAULLE", "postcode": "92200", "city": "NEUILLY SUR SEINE" },
"next_run_at": "2026-10-16T09:00:00Z",
"last_run_at": "2026-09-16T09:00:00Z",
"last_valid": true,
"last_confidence": "confirmed",
"last_check_id": "chk_01J9X0000000000000000000",
"last_check_url": "https://api.akwito.eu/v1/checks/chk_01J9X0000000000000000000",
"changed_at": null,
"created_at": "2026-09-16T09:00:00Z"
}
candidate is null when the watch runs in lookup mode. The last_* fields are the state of the last run: it is what the next run compares itself to. They are all null on a fresh watch — null, never an empty string — and last_check_id and last_check_url also become null when retention purges the proof — the next run then behaves like a first run and announces nothing. changed_at is the date of the last change, not of the last run.
POST /v1/watches #
{
"vat_number": "FR40303265045",
"label": "Chanel",
"candidate": { "name": "CHANEL", "street": "", "postcode": "92200", "city": "NEUILLY SUR SEINE" },
"frequency": "monthly"
}
201 Created with the watch object. Only vat_number is required. frequency defaults to monthly. A candidate with a non-empty name runs every check in verify mode; without a name it is ignored, since there is nothing to confirm. The first check is scheduled immediately (next_run_at is now), so the watch has a reference state within the hour.
| HTTP status | error.code | When |
|---|---|---|
| 400 | INVALID_FORMAT | vat_number could not be normalised |
| 400 | BAD_REQUEST | malformed JSON, unknown field, or a frequency other than monthly/weekly/daily |
| 403 | WATCH_LIMIT | the plan's watch limit is reached; carries limit |
| 403 | FREQUENCY_NOT_ALLOWED | weekly or daily on a plan that does not sell it |
| 409 | WATCH_EXISTS | this account already watches this number |
POST /v1/watches/import #
{ "batch_id": "bat_01J9X0000000000000000000" }
Watches every done line of a batch, monthly. Lines that are still pending or failed are ignored: they carry no usable number.
{ "created": 812, "skipped_existing": 14, "skipped_limit": 0, "skipped_invalid": 0 }
201 Created. skipped_existing counts the numbers already watched, skipped_limit those left out because the plan's limit was reached, and skipped_invalid those whose stored number can no longer be normalised. The four counters add up to the batch's done lines: nothing is left out in silence. An unknown batch, or one owned by another account, is 404 NOT_FOUND.
A line's identity columns follow the same rule as POST /v1/watches: a candidate is kept only when it carries a name, so a verify line that held an address and no name becomes a lookup watch.
GET /v1/watches #
Lists the account's watches, newest first. limit (1 to 100, default 20) and cursor behave as on the other lists; items have the shape of the watch object.
GET /v1/watches/{id} #
The watch object, or 404 NOT_FOUND when it does not exist or belongs to another account.
DELETE /v1/watches/{id} #
Removes the watch and its change log, 204 No Content. The checks it produced are kept: they are evidence, and they remain readable at their own proof_url.
GET /v1/watches/events #
The account's change log, newest first, limit and cursor as above.
{
"items": [
{
"event_id": "wev_01J9X0000000000000000000",
"watch_id": "wat_01J9X0000000000000000000",
"kind": "became_invalid",
"check_id": "chk_01J9X0000000000000000000",
"check_url": "https://api.akwito.eu/v1/checks/chk_01J9X0000000000000000000",
"before": { "valid": true, "confidence": "confirmed", "name": "CHANEL", "address": "135 AV CHARLES DE GAULLE, 92200 NEUILLY SUR SEINE" },
"after": { "valid": false, "confidence": "invalid" },
"created_at": "2026-09-16T09:00:00Z"
}
],
"next_cursor": ""
}
kind is one of became_invalid, became_valid, identity_changed, confidence_changed; a single run can produce several. before and after are rendered as they were logged, so an event stays readable even once the check it points to has been purged.